From 8a648e7e22743ce7193e8c83f2ce1457cccf3f9b Mon Sep 17 00:00:00 2001 From: Serge Noiraud Date: Sat, 19 Aug 2017 19:54:15 +0200 Subject: [PATCH] Reorganization of the narrative web report. (#445) * Reorganization of the narrative web report. Resolves #010151, #07740 * Add empty role to person and family backref lists --- gramps/plugins/webreport/addressbook.py | 120 + gramps/plugins/webreport/addressbooklist.py | 170 + gramps/plugins/webreport/basepage.py | 2801 ++++++ gramps/plugins/webreport/citation.py | 80 + gramps/plugins/webreport/common.py | 863 ++ gramps/plugins/webreport/contact.py | 139 + gramps/plugins/webreport/download.py | 195 + gramps/plugins/webreport/event.py | 448 + gramps/plugins/webreport/family.py | 399 + gramps/plugins/webreport/home.py | 105 + gramps/plugins/webreport/introduction.py | 106 + gramps/plugins/webreport/media.py | 641 ++ gramps/plugins/webreport/narrativeweb.py | 8755 +------------------ gramps/plugins/webreport/person.py | 1787 ++++ gramps/plugins/webreport/place.py | 451 + gramps/plugins/webreport/repository.py | 287 + gramps/plugins/webreport/source.py | 306 + gramps/plugins/webreport/statistics.py | 243 + gramps/plugins/webreport/surname.py | 265 + gramps/plugins/webreport/surnamelist.py | 249 + gramps/plugins/webreport/thumbnail.py | 277 + gramps/plugins/webreport/webplugins.gpr.py | 6 +- 22 files changed, 10019 insertions(+), 8674 deletions(-) create mode 100644 gramps/plugins/webreport/addressbook.py create mode 100644 gramps/plugins/webreport/addressbooklist.py create mode 100644 gramps/plugins/webreport/basepage.py create mode 100644 gramps/plugins/webreport/citation.py create mode 100644 gramps/plugins/webreport/common.py create mode 100644 gramps/plugins/webreport/contact.py create mode 100644 gramps/plugins/webreport/download.py create mode 100644 gramps/plugins/webreport/event.py create mode 100644 gramps/plugins/webreport/family.py create mode 100644 gramps/plugins/webreport/home.py create mode 100644 gramps/plugins/webreport/introduction.py create mode 100644 gramps/plugins/webreport/media.py create mode 100644 gramps/plugins/webreport/person.py create mode 100644 gramps/plugins/webreport/place.py create mode 100644 gramps/plugins/webreport/repository.py create mode 100644 gramps/plugins/webreport/source.py create mode 100644 gramps/plugins/webreport/statistics.py create mode 100644 gramps/plugins/webreport/surname.py create mode 100644 gramps/plugins/webreport/surnamelist.py create mode 100644 gramps/plugins/webreport/thumbnail.py diff --git a/gramps/plugins/webreport/addressbook.py b/gramps/plugins/webreport/addressbook.py new file mode 100644 index 000000000..68de3b270 --- /dev/null +++ b/gramps/plugins/webreport/addressbook.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + AddressBookPage +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.plug.report import Bibliography +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import FULLCLEAR + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +class AddressBookPage(BasePage): + """ + Create one page for one Address + """ + def __init__(self, report, title, person_handle, has_add, has_res, has_url): + """ + @param: report -- The instance of the main report class + for this report + @param: title -- Is the title of the web page + @param: person_handle -- the url, address and residence to use + for the report + @param: has_add -- the address to use for the report + @param: has_res -- the residence to use for the report + @param: has_url -- the url to use for the report + """ + person = report.database.get_person_from_handle(person_handle) + BasePage.__init__(self, report, title, person.gramps_id) + self.bibli = Bibliography() + + self.uplink = True + + # set the file name and open file + output_file, sio = self.report.create_file(person_handle, "addr") + addressbookpage, head, body = self.write_header(_("Address Book")) + + # begin address book page division and section title + with Html("div", class_="content", + id="AddressBookDetail") as addressbookdetail: + body += addressbookdetail + + link = self.new_person_link(person_handle, uplink=True, + person=person) + addressbookdetail += Html("h3", link) + + # individual has an address + if has_add: + addressbookdetail += self.display_addr_list(has_add, None) + + # individual has a residence + if has_res: + addressbookdetail.extend( + self.dump_residence(res) + for res in has_res + ) + + # individual has a url + if has_url: + addressbookdetail += self.display_url_list(has_url) + + # add fullclear for proper styling + # and footer section to page + footer = self.write_footer(None) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(addressbookpage, output_file, sio, 0) diff --git a/gramps/plugins/webreport/addressbooklist.py b/gramps/plugins/webreport/addressbooklist.py new file mode 100644 index 000000000..cf1c05d87 --- /dev/null +++ b/gramps/plugins/webreport/addressbooklist.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + AddressBookListPage +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import FULLCLEAR + +LOG = logging.getLogger(".NarrativeWeb") +_ = glocale.translation.sgettext +getcontext().prec = 8 + +class AddressBookListPage(BasePage): + """ + Create the index for addresses. + """ + def __init__(self, report, title, has_url_addr_res): + """ + @param: report -- The instance of the main report class + for this report + @param: title -- Is the title of the web page + @param: has_url_addr_res -- The url, address and residence to use + for the report + """ + BasePage.__init__(self, report, title) + + # Name the file, and create it + output_file, sio = self.report.create_file("addressbook") + + # Add xml, doctype, meta and stylesheets + addressbooklistpage, head, body = self.write_header(_("Address Book")) + + # begin AddressBookList division + with Html("div", class_="content", + id="AddressBookList") as addressbooklist: + body += addressbooklist + + # Address Book Page message + msg = _("This page contains an index of all the individuals in " + "the database, sorted by their surname, with one of the " + "following: Address, Residence, or Web Links. " + "Selecting the person’s name will take you " + "to their individual Address Book page.") + addressbooklist += Html("p", msg, id="description") + + # begin Address Book table + with Html("table", + class_="infolist primobjlist addressbook") as table: + addressbooklist += table + + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + trow.extend( + Html("th", label, class_=colclass, inline=True) + for (label, colclass) in [ + [" ", "ColumnRowLabel"], + [_("Full Name"), "ColumnName"], + [_("Address"), "ColumnAddress"], + [_("Residence"), "ColumnResidence"], + [_("Web Links"), "ColumnWebLinks"] + ] + ) + + tbody = Html("tbody") + table += tbody + + index = 1 + for (sort_name, person_handle, + has_add, has_res, + has_url) in has_url_addr_res: + + address = None + residence = None + weblinks = None + + # has address but no residence event + if has_add and not has_res: + address = "X" + + # has residence, but no addresses + elif has_res and not has_add: + residence = "X" + + # has residence and addresses too + elif has_add and has_res: + address = "X" + residence = "X" + + # has Web Links + if has_url: + weblinks = "X" + + trow = Html("tr") + tbody += trow + + trow.extend( + Html("td", data or " ", class_=colclass, + inline=True) + for (colclass, data) in [ + ["ColumnRowLabel", index], + ["ColumnName", + self.addressbook_link(person_handle)], + ["ColumnAddress", address], + ["ColumnResidence", residence], + ["ColumnWebLinks", weblinks] + ] + ) + index += 1 + + # Add footer and clearline + footer = self.write_footer(None) + body += (FULLCLEAR, footer) + + # send the page out for processing + # and close the file + self.xhtml_writer(addressbooklistpage, output_file, sio, 0) diff --git a/gramps/plugins/webreport/basepage.py b/gramps/plugins/webreport/basepage.py new file mode 100644 index 000000000..7b1608ea9 --- /dev/null +++ b/gramps/plugins/webreport/basepage.py @@ -0,0 +1,2801 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + BasePage - super class for producing a web page. This class is instantiated + once for each page. Provdes various common functions. +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from functools import partial +import os +import copy +import datetime +from decimal import getcontext + +#------------------------------------------------ +# Set up logging +#------------------------------------------------ +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.lib import (FamilyRelType, NoteType, NameType, Person, UrlType, + Name, PlaceType, EventRoleType, Family, Citation) +from gramps.gen.lib.date import Today +from gramps.gen.const import PROGRAM_NAME, URL_HOMEPAGE +from gramps.version import VERSION +from gramps.gen.plug.report import Bibliography +from gramps.gen.plug.report import utils +from gramps.gen.utils.config import get_researcher +from gramps.gen.utils.string import conf_strings +from gramps.gen.utils.file import media_path_full +from gramps.gen.utils.thumbnails import get_thumbnail_path +from gramps.gen.display.name import displayer as _nd +from gramps.gen.display.place import displayer as _pd +from gramps.plugins.lib.libhtmlconst import _CC +from gramps.gen.utils.db import get_birth_or_fallback, get_death_or_fallback +from gramps.plugins.lib.libhtml import Html, xml_lang +from gramps.plugins.lib.libhtmlbackend import HtmlBackend, process_spaces +from gramps.gen.utils.place import conv_lat_lon +from gramps.gen.utils.location import get_main_location +from gramps.plugins.webreport.common import (_NAME_STYLE_DEFAULT, HTTP, + _NAME_STYLE_FIRST, HTTPS, + _get_short_name, + add_birthdate, CSS, html_escape, + _NARRATIVESCREEN, _NARRATIVEPRINT, + FULLCLEAR, _has_webpage_extension) + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +class BasePage: # pylint: disable=C1001 + """ + Manages all the functions, variables, and everything needed + for all of the classes contained within this plugin + """ + def __init__(self, report, title, gid=None): + """ + @param: report -- The instance of the main report class for + this report + @param: title -- Is the title of the web page + @param: gid -- The family gramps ID + """ + self.uplink = False + # class to do conversion of styled notes to html markup + self._backend = HtmlBackend() + self._backend.build_link = report.build_link + + self.report = report + self.r_db = report.database + self.r_user = report.user + self.title_str = title + self.gid = gid + self.bibli = Bibliography() + + self.page_title = "" + + self.author = get_researcher().get_name() + if self.author: + self.author = self.author.replace(',,,', '') + + # 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. + self.html_dir = report.options['target'] + self.ext = report.options['ext'] + self.noid = report.options['nogid'] + self.linkhome = report.options['linkhome'] + self.create_media = report.options['gallery'] + self.create_unused_media = report.options['unused'] + self.create_thumbs_only = report.options['create_thumbs_only'] + self.inc_families = report.options['inc_families'] + self.inc_events = report.options['inc_events'] + self.usecms = report.options['usecms'] + self.target_uri = report.options['cmsuri'] + self.usecal = report.options['usecal'] + self.target_cal_uri = report.options['caluri'] + self.familymappages = None + lang = report.options['trans'] + self.rlocale = report.set_locale(lang) + self._ = self.rlocale.translation.sgettext + self.colon = self._(':') # translators: needed for French, else ignore + + if report.options['securesite']: + self.secure_mode = HTTPS + else: + self.secure_mode = HTTP + + # Functions used when no Web Page plugin is provided + def add_instance(self, *param): + """ + Add an instance + """ + pass + + def display_pages(self, title): + """ + Display the pages + """ + pass + + def sort_on_name_and_grampsid(self, handle): + """ Used to sort on name and gramps ID. """ + person = self.r_db.get_person_from_handle(handle) + name = _nd.display(person) + return (name, person.get_gramps_id()) + + def sort_on_grampsid(self, event_ref): + """ + Sort on gramps ID + """ + evt = self.r_db.get_event_from_handle( + event_ref.ref) + return evt.get_gramps_id() + + def copy_thumbnail(self, handle, photo, region=None): + """ + Given a handle (and optional region) make (if needed) an + up-to-date cache of a thumbnail, and call report.copy_file + to copy the cached thumbnail to the website. + Return the new path to the image. + """ + to_dir = self.report.build_path('thumb', handle) + to_path = os.path.join(to_dir, handle) + ( + ('%d,%d-%d,%d.png' % region) if region else '.png' + ) + + if photo.get_mime_type(): + full_path = media_path_full(self.r_db, photo.get_path()) + from_path = get_thumbnail_path(full_path, + photo.get_mime_type(), + region) + if not os.path.isfile(from_path): + from_path = CSS["Document"]["filename"] + else: + from_path = CSS["Document"]["filename"] + self.report.copy_file(from_path, to_path) + return to_path + + def get_nav_menu_hyperlink(self, url_fname, nav_text): + """ + Returns the navigation menu hyperlink + """ + if url_fname == self.target_cal_uri: + uplink = False + else: + uplink = self.uplink + + # check for web page file extension? + if not _has_webpage_extension(url_fname): + url_fname += self.ext + + # get menu item url and begin hyperlink... + url = self.report.build_url_fname(url_fname, None, uplink) + + return Html("a", nav_text, href=url, title=nav_text, inline=True) + + def get_column_data(self, unordered, data_list, column_title): + """ + Returns the menu column for Drop Down Menus and Drop Down Citations + """ + if len(data_list) == 0: + return + + elif len(data_list) == 1: + url_fname, nav_text = data_list[0][0], data_list[0][1] + hyper = self.get_nav_menu_hyperlink(url_fname, nav_text) + unordered.extend( + Html("li", hyper, inline=True) + ) + else: + col_list = Html("li") + ( + Html("a", column_title, href="#", + title=column_title, inline=True) + ) + unordered += col_list + + unordered1 = Html("ul") + col_list += unordered1 + + for url_fname, nav_text in data_list: + hyper = self.get_nav_menu_hyperlink(url_fname, nav_text) + unordered1.extend(Html("li", hyper, inline=True)) + + def display_relationships(self, individual, place_lat_long): + """ + Displays a person's relationships ... + + @param: family_handle_list -- families in this report database + @param: place_lat_long -- for use in Family Map Pages. This will be None + if called from Family pages, which do not create a Family Map + """ + family_list = individual.get_family_handle_list() + if not family_list: + return None + + with Html("div", class_="subsection", id="families") as section: + section += Html("h4", self._("Families"), inline=True) + + table_class = "infolist" + if len(family_list) > 1: + table_class += " fixed_subtables" + with Html("table", class_=table_class) as table: + section += table + + for family_handle in family_list: + family = self.r_db.get_family_from_handle(family_handle) + if family: + link = self.family_link( + family_handle, + self.report.obj_dict[Family][family_handle][1], + gid=family.get_gramps_id(), uplink=True) + trow = Html("tr", class_="BeginFamily") + ( + Html("td", " ", class_="ColumnType", + inline=True), + Html("td", " ", class_="ColumnAttribute", + inline=True), + Html("td", link, class_="ColumnValue", + inline=True) + ) + table += trow + # find the spouse of the principal individual and + # display that person + sp_hdl = utils.find_spouse(individual, family) + if sp_hdl: + spouse = self.r_db.get_person_from_handle(sp_hdl) + if spouse: + table += self.display_spouse(spouse, family, + place_lat_long) + + details = self.display_family_details(family, + place_lat_long) + if details is not None: + table += details + return section + + def display_family_relationships(self, family, place_lat_long): + """ + Displays a family's relationships ... + + @param: family -- the family to be displayed + @param: place_lat_long -- for use in Family Map Pages. This will be None + if called from Family pages, which do not create a Family Map + """ + with Html("div", class_="subsection", id="families") as section: + section += Html("h4", self._("Families"), inline=True) + + table_class = "infolist" + with Html("table", class_=table_class) as table: + section += table + for person_hdl in [family.get_father_handle(), + family.get_mother_handle()]: + person = None + if person_hdl: + person = self.r_db.get_person_from_handle(person_hdl) + if person: + table += self.display_spouse(person, + family, place_lat_long) + + details = self.display_family_details(family, place_lat_long) + if details is not None: + table += details + return section + + def display_family_details(self, family, place_lat_long): + """ + Display details about one family: family events, children, family LDS + ordinances, family attributes + """ + table = None + birthorder = self.report.options["birthorder"] + # display family events; such as marriage and divorce... + family_events = family.get_event_ref_list() + if family_events: + trow = Html("tr") + ( + Html("td", " ", class_="ColumnType", inline=True), + Html("td", " ", class_="ColumnAttribute", inline=True), + Html("td", self.format_family_events(family_events, + place_lat_long), + class_="ColumnValue") + ) + table = trow + + # If the families pages are not output, display family notes + if not self.inc_families: + notelist = family.get_note_list() + for notehandle in notelist: + note = self.r_db.get_note_from_handle(notehandle) + if note: + trow = Html("tr") + ( + Html("td", " ", class_="ColumnType", inline=True), + Html("td", self._("Narrative"), + class_="ColumnAttribute", + inline=True), + Html("td", self.get_note_format(note, True), + class_="ColumnValue") + ) + table = table + trow if table is not None else trow + + childlist = family.get_child_ref_list() + if childlist: + trow = Html("tr") + ( + Html("td", " ", class_="ColumnType", inline=True), + Html("td", self._("Children"), class_="ColumnAttribute", + inline=True) + ) + table = table + trow if table is not None else trow + + tcell = Html("td", class_="ColumnValue", close=False) + trow += tcell + + with Html("table", class_="infolist eventlist") as table2: + thead = Html("thead") + table2 += thead + header = Html("tr") + + header.extend( + Html("th", label, class_=colclass, inline=True) + for (label, colclass) in [ + [self._("Name"), "ColumnName"], + [self._("Birth Date"), "ColumnDate"], + [self._("Death Date"), "ColumnDate"], + ] + ) + thead += header + + # begin table body + tbody = Html("tbody") + table2 += tbody + + childlist = [child_ref.ref for child_ref in childlist] + + # add individual's children event places to family map... + if self.familymappages: + for handle in childlist: + child = self.r_db.get_person_from_handle(handle) + if child: + self._get_event_place(child, place_lat_long) + + children = add_birthdate(self.r_db, childlist, self.rlocale) + if birthorder: + children = sorted(children) + + tbody.extend((Html("tr", inline=True) + + Html("td", inline=True, close=False) + + self.display_child_link(chandle) + + Html("td", birth, inline=True) + + Html("td", death, inline=True)) + for birth_date, birth, death, chandle in children + ) + trow += table2 + + # family LDS ordinance list + family_lds_ordinance_list = family.get_lds_ord_list() + if family_lds_ordinance_list: + trow = Html("tr") + ( + Html("td", " ", class_="ColumnType", inline=True), + Html("td", self._("LDS Ordinance"), class_="ColumnAttribute", + inline=True), + Html("td", self.dump_ordinance(family, "Family"), + class_="ColumnValue") + ) + table = table + trow if table is not None else trow + + # Family Attribute list + family_attribute_list = family.get_attribute_list() + if family_attribute_list: + trow = Html("tr") + ( + Html("td", " ", class_="ColumnType", inline=True), + Html("td", self._("Attributes"), class_="ColumnAttribute", + inline=True) + ) + table = table + trow if table is not None else trow + + tcell = Html("td", class_="ColumnValue") + trow += tcell + + # we do not need the section variable for this instance + # of Attributes... + dummy, attrtable = self.display_attribute_header() + tcell += attrtable + self.display_attr_list(family_attribute_list, attrtable) + return table + + def complete_people(self, tcell, first_person, handle_list, uplink=True): + """ + completes the person column for classes EventListPage and EventPage + + @param: tcell -- table cell from its caller + @param: first_person -- Not used any more, done via css + @param: handle_list -- handle list from the backlink of the event_handle + """ + for (classname, handle) in handle_list: + + # personal event + if classname == "Person": + tcell += Html("span", self.new_person_link(handle, uplink), + class_="person", inline=True) + + # family event + else: + _obj = self.r_db.get_family_from_handle(handle) + if _obj: + + # husband and spouse in this example, + # are called father and mother + husband_handle = _obj.get_father_handle() + if husband_handle: + hlink = self.new_person_link(husband_handle, uplink) + spouse_handle = _obj.get_mother_handle() + if spouse_handle: + slink = self.new_person_link(spouse_handle, uplink) + + if spouse_handle and husband_handle: + tcell += Html("span", hlink, class_="father", + inline=True) + tcell += Html("span", slink, class_="mother", + inline=True) + elif spouse_handle: + tcell += Html("span", slink, class_="mother", + inline=True) + elif husband_handle: + tcell += Html("span", hlink, class_="father", + inline=True) + return tcell + + def dump_attribute(self, attr): + """ + dump attribute for object presented in display_attr_list() + + @param: attr = attribute object + """ + trow = Html("tr") + + trow.extend( + Html("td", data or " ", class_=colclass, + inline=True if (colclass == "Type" or "Sources") else False) + for (data, colclass) in [ + (str(attr.get_type()), "ColumnType"), + (attr.get_value(), "ColumnValue"), + (self.dump_notes(attr.get_note_list()), "ColumnNotes"), + (self.get_citation_links(attr.get_citation_list()), + "ColumnSources") + ] + ) + return trow + + def get_citation_links(self, citation_handle_list): + """ + get citation link from the citation handle list + + @param: citation_handle_list = list of gen/lib/Citation + """ + text = "" + for citation_handle in citation_handle_list: + citation = self.r_db.get_citation_from_handle(citation_handle) + if citation: + index, key = self.bibli.add_reference(citation) + id_ = "%d%s" % (index+1, key) + text += ' %s' % (id_, id_) + return text + + def get_note_format(self, note, link_prefix_up): + """ + will get the note from the database, and will return either the + styled text or plain note + """ + self.report.link_prefix_up = link_prefix_up + + text = "" + if note is not None: + # retrieve the body of the note + note_text = note.get() + + # styled notes + htmlnotetext = self.styled_note( + note.get_styledtext(), note.get_format(), + contains_html=(note.get_type() == NoteType.HTML_CODE)) + text = htmlnotetext or Html("p", note_text) + + # return text of the note to its callers + return text + + def styled_note(self, styledtext, styled_format, contains_html=False): + """ + styledtext : assumed a StyledText object to write + styled_format : = 0 : Flowed, = 1 : Preformatted + style_name : name of the style to use for default presentation + """ + text = str(styledtext) + + if not text: + return '' + + s_tags = styledtext.get_tags() + htmllist = Html("div", class_="grampsstylednote") + if contains_html: + markuptext = self._backend.add_markup_from_styled(text, + s_tags, + split='\n', + escape=False) + htmllist += markuptext + else: + markuptext = self._backend.add_markup_from_styled(text, + s_tags, + split='\n') + linelist = [] + linenb = 1 + for line in markuptext.split('\n'): + [line, sigcount] = process_spaces(line, styled_format) + if sigcount == 0: + # The rendering of an empty paragraph '

' + # is undefined so we use a non-breaking space + if linenb == 1: + linelist.append(' ') + htmllist.extend(Html('p') + linelist) + linelist = [] + linenb = 1 + else: + if linenb > 1: + linelist[-1] += '
' + linelist.append(line) + linenb += 1 + if linenb > 1: + htmllist.extend(Html('p') + linelist) + # if the last line was blank, then as well as outputting + # the previous para, which we have just done, + # we also output a new blank para + if sigcount == 0: + linelist = [" "] + htmllist.extend(Html('p') + linelist) + return htmllist + + def dump_notes(self, notelist): + """ + dump out of list of notes with very little elements of its own + + @param: notelist -- list of notes + """ + if not notelist: + return Html("div") + + # begin unordered list + notesection = Html("div") + for notehandle in notelist: + this_note = self.r_db.get_note_from_handle(notehandle) + if this_note is not None: + notesection.extend(Html("i", self._(this_note.type.xml_str()), + class_="NoteType")) + notesection.extend(self.get_note_format(this_note, True)) + return notesection + + def event_header_row(self): + """ + creates the event header row for all events + """ + trow = Html("tr") + trow.extend( + Html("th", trans, class_=colclass, inline=True) + for trans, colclass in [ + (self._("Event"), "ColumnEvent"), + (self._("Date"), "ColumnDate"), + (self._("Place"), "ColumnPlace"), + (self._("Description"), "ColumnDescription"), + (self._("Notes"), "ColumnNotes"), + (self._("Sources"), "ColumnSources")] + ) + return trow + + def display_event_row(self, event, event_ref, place_lat_long, + uplink, hyperlink, omit): + """ + display the event row for IndividualPage + + @param: evt -- Event object from report database + @param: evt_ref -- Event reference + @param: place_lat_long -- For use in Family Map Pages. This will be None + if called from Family pages, which do not + create a Family Map + @param: uplink -- If True, then "../../../" is inserted in front + of the result. + @param: hyperlink -- Add a hyperlink or not + @param: omit -- Role to be omitted in output + """ + event_gid = event.get_gramps_id() + + place_handle = event.get_place_handle() + if place_handle: + place = self.r_db.get_place_from_handle(place_handle) + if place: + self.append_to_place_lat_long(place, event, place_lat_long) + + # begin event table row + trow = Html("tr") + + # get event type and hyperlink to it or not? + etype = self._(event.get_type().xml_str()) + + event_role = event_ref.get_role() + if not event_role == omit: + etype += " (%s)" % event_role + event_hyper = self.event_link(event_ref.ref, + etype, + event_gid, + uplink) if hyperlink else etype + trow += Html("td", event_hyper, class_="ColumnEvent") + + # get event data + event_data = self.get_event_data(event, event_ref, uplink) + + trow.extend( + Html("td", data or " ", class_=colclass, + inline=(not data or colclass == "ColumnDate")) + for (label, colclass, data) in event_data + ) + + # get event notes + notelist = event.get_note_list() + notelist.extend(event_ref.get_note_list()) + htmllist = self.dump_notes(notelist) + + # if the event or event reference has an attribute attached to it, + # get the text and format it correctly? + attrlist = event.get_attribute_list() + attrlist.extend(event_ref.get_attribute_list()) + for attr in attrlist: + htmllist.extend(Html("p", + _("%(str1)s: %(str2)s") % { + 'str1' : Html("b", attr.get_type()), + 'str2' : attr.get_value() + })) + + #also output notes attached to the attributes + notelist = attr.get_note_list() + if notelist: + htmllist.extend(self.dump_notes(notelist)) + + trow += Html("td", htmllist, class_="ColumnNotes") + + # get event source references + srcrefs = self.get_citation_links(event.get_citation_list()) or " " + trow += Html("td", srcrefs, class_="ColumnSources") + + # return events table row to its callers + return trow + + def append_to_place_lat_long(self, place, event, place_lat_long): + """ + Create a list of places with coordinates. + + @param: place_lat_long -- for use in Family Map Pages. This will be None + if called from Family pages, which do not create a Family Map + """ + if place_lat_long is None: + return + place_handle = place.get_handle() + event_date = event.get_date_object() + + # 0 = latitude, 1 = longitude, 2 - placetitle, + # 3 = place handle, 4 = event date, 5 = event type + found = any(data[3] == place_handle and data[4] == event_date + for data in place_lat_long) + if not found: + placetitle = _pd.display(self.r_db, place) + latitude = place.get_latitude() + longitude = place.get_longitude() + if latitude and longitude: + latitude, longitude = conv_lat_lon(latitude, longitude, "D.D8") + if latitude is not None: + etype = event.get_type() + place_lat_long.append([latitude, longitude, placetitle, + place_handle, event_date, etype]) + + def _get_event_place(self, person, place_lat_long): + """ + Retrieve from a person their events, and places for family map + + @param: person -- Person object from the database + @param: place_lat_long -- For use in Family Map Pages. This will be + None if called from Family pages, which do + not create a Family Map + """ + if not person: + return + + # check to see if this person is in the report database? + use_link = self.report.person_in_webreport(person.get_handle()) + if use_link: + evt_ref_list = person.get_event_ref_list() + if evt_ref_list: + for evt_ref in evt_ref_list: + event = self.r_db.get_event_from_handle(evt_ref.ref) + if event: + pl_handle = event.get_place_handle() + if pl_handle: + place = self.r_db.get_place_from_handle(pl_handle) + if place: + self.append_to_place_lat_long(place, event, + place_lat_long) + + def family_link(self, family_handle, name, gid=None, uplink=False): + """ + Create the url and link for FamilyPage + + @param: family_handle -- The handle for the family to link + @param: name -- The family name + @param: gid -- The family gramps ID + @param: uplink -- If True, then "../../../" is inserted in front + of the result. + """ + name = html_escape(name) + if not self.noid and gid: + gid_html = Html("span", " [%s]" % gid, class_="grampsid", + inline=True) + else: + gid_html = "" # pylint: disable=redefined-variable-type + + result = self.report.obj_dict.get(Family).get(family_handle) + if result is None: + # the family is not included in the webreport + return name + str(gid_html) + + url = self.report.build_url_fname(result[0], uplink=uplink) + hyper = Html("a", name, href=url, title=name) + hyper += gid_html + return hyper + + def get_family_string(self, family): + """ + Unused method ??? + Returns a hyperlink for each person linked to the Family Page + + @param: family -- The family + """ + husband, spouse = [False]*2 + + husband_handle = family.get_father_handle() + + if husband_handle: + husband = self.r_db.get_person_from_handle(husband_handle) + else: + husband = None + + spouse_handle = family.get_mother_handle() + if spouse_handle: + spouse = self.r_db.get_person_from_handle(spouse_handle) + else: + spouse = None + + if husband: + husband_name = self.get_name(husband) + hlink = self.family_link(family.get_handle(), + husband_name, uplink=self.uplink) + if spouse: + spouse_name = self.get_name(spouse) + slink = self.family_link(family.get_handle(), + spouse_name, uplink=self.uplink) + + title_str = '' + if husband and spouse: + title_str = '%s ' % hlink + self._("and") + ' %s' % slink + elif husband: + title_str = '%s ' % hlink + elif spouse: + title_str = '%s ' % slink + return title_str + + def event_link(self, event_handle, event_title, gid=None, uplink=False): + """ + Creates a hyperlink for an event based on its type + + @param: event_handle -- Event handle + @param: event_title -- Event title + @param: gid -- The gramps ID for the event + @param: uplink -- If True, then "../../../" is inserted in front + of the result. + """ + if not self.inc_events: + return event_title + + url = self.report.build_url_fname_html(event_handle, "evt", uplink) + hyper = Html("a", event_title, href=url, title=event_title) + + if not self.noid and gid: + hyper += Html("span", " [%s]" % gid, class_="grampsid", inline=True) + return hyper + + def format_family_events(self, event_ref_list, place_lat_long): + """ + displays the event row for events such as marriage and divorce + + @param: event_ref_list -- List of events reference + @param: place_lat_long -- For use in Family Map Pages. This will be None + if called from Family pages, which do not + create a Family Map + """ + with Html("table", class_="infolist eventlist") as table: + thead = Html("thead") + table += thead + + # attach event header row + thead += self.event_header_row() + + # begin table body + tbody = Html("tbody") + table += tbody + + for evt_ref in event_ref_list: + event = self.r_db.get_event_from_handle(evt_ref.ref) + + # add event body row + tbody += self.display_event_row(event, evt_ref, place_lat_long, + uplink=True, hyperlink=True, + omit=EventRoleType.FAMILY) + return table + + def get_event_data(self, evt, evt_ref, + uplink, gid=None): + """ + retrieve event data from event and evt_ref + + @param: evt -- Event from database + @param: evt_ref -- Event reference + @param: uplink -- If True, then "../../../" is inserted in front of + the result. + """ + place = None + place_handle = evt.get_place_handle() + if place_handle: + place = self.r_db.get_place_from_handle(place_handle) + + place_hyper = None + if place: + place_name = _pd.display(self.r_db, place, evt.get_date_object()) + place_hyper = self.place_link(place_handle, place_name, + uplink=uplink) + + evt_desc = evt.get_description() + + # wrap it all up and return to its callers + # position 0 = translatable label, position 1 = column class + # position 2 = data + return [(self._("Date"), "ColumnDate", + self.rlocale.get_date(evt.get_date_object())), + (self._("Place"), "ColumnPlace", place_hyper), + (self._("Description"), "ColumnDescription", evt_desc)] + + def dump_ordinance(self, ldsobj, ldssealedtype): + """ + will dump the LDS Ordinance information for either + a person or a family ... + + @param: ldsobj -- Either person or family + @param: ldssealedtype -- Either Sealed to Family or Spouse + """ + objectldsord = ldsobj.get_lds_ord_list() + if not objectldsord: + return None + + # begin LDS ordinance table and table head + with Html("table", class_="infolist ldsordlist") as table: + thead = Html("thead") + table += thead + + # begin HTML row + trow = Html("tr") + thead += trow + + trow.extend( + Html("th", label, class_=colclass, inline=True) + for (label, colclass) in [ + [self._("Type"), "ColumnLDSType"], + [self._("Date"), "ColumnDate"], + [self._("Temple"), "ColumnLDSTemple"], + [self._("Place"), "ColumnLDSPlace"], + [self._("Status"), "ColumnLDSStatus"], + [self._("Sources"), "ColumnLDSSources"] + ] + ) + + # start table body + tbody = Html("tbody") + table += tbody + + for ordobj in objectldsord: + place_hyper = " " + place_handle = ordobj.get_place_handle() + if place_handle: + place = self.r_db.get_place_from_handle(place_handle) + if place: + place_title = _pd.display(self.r_db, place) + place_hyper = self.place_link( # pylint: disable=R0204 + place_handle, place_title, + place.get_gramps_id(), uplink=True) + + # begin ordinance rows + trow = Html("tr") + + trow.extend( + Html("td", value or " ", class_=colclass, + inline=(not value or colclass == "ColumnDate")) + for (value, colclass) in [ + (ordobj.type2xml(), "ColumnType"), + (self.rlocale.get_date(ordobj.get_date_object()), + "ColumnDate"), + (ordobj.get_temple(), "ColumnLDSTemple"), + (place_hyper, "ColumnLDSPlace"), + (ordobj.get_status(), "ColumnLDSStatus"), + (self.get_citation_links(ordobj.get_citation_list()), + "ColumnSources") + ] + ) + tbody += trow + return table + + def write_srcattr(self, srcattr_list): + """ + Writes out the srcattr for the different objects + + @param: srcattr_list -- List of source attributes + """ + if len(srcattr_list) == 0: + return None + + # begin data map division and section title... + with Html("div", class_="subsection", id="data_map") as section: + section += Html("h4", self._("Attributes"), inline=True) + + with Html("table", class_="infolist") as table: + section += table + + thead = Html("thead") + table += thead + + trow = Html("tr") + ( + Html("th", self._("Key"), class_="ColumnAttribute", + inline=True), + Html("th", self._("Value"), class_="ColumnValue", + inline=True) + ) + thead += trow + + tbody = Html("tbody") + table += tbody + + for srcattr in srcattr_list: + trow = Html("tr") + ( + Html("td", str(srcattr.get_type()), + class_="ColumnAttribute", inline=True), + Html("td", srcattr.get_value(), + class_="ColumnValue", inline=True) + ) + tbody += trow + return section + + def source_link(self, source_handle, source_title, + gid=None, cindex=None, uplink=False): + """ + Creates a link to the source object + + @param: source_handle -- Source handle from database + @param: source_title -- Title from the source object + @param: gid -- Source gramps id from the source object + @param: cindex -- Count index + @param: uplink -- If True, then "../../../" is inserted in front + of the result. + """ + url = self.report.build_url_fname_html(source_handle, "src", uplink) + hyper = Html("a", source_title, + href=url, + title=source_title) + + # if not None, add name reference to hyperlink element + if cindex: + hyper.attr += ' name ="sref%d"' % cindex + + # add Gramps ID + if not self.noid and gid: + hyper += Html("span", ' [%s]' % gid, class_="grampsid", inline=True) + return hyper + + def display_addr_list(self, addrlist, showsrc): + """ + Display a person's or repository's addresses ... + + @param: addrlist -- a list of address handles + @param: showsrc -- True = show sources + False = do not show sources + None = djpe + """ + if not addrlist: + return None + + # begin addresses division and title + with Html("div", class_="subsection", id="Addresses") as section: + section += Html("h4", self._("Addresses"), inline=True) + + # write out addresses() + section += self.dump_addresses(addrlist, showsrc) + + # return address division to its caller + return section + + def dump_addresses(self, addrlist, showsrc): + """ + will display an object's addresses, url list, note list, + and source references. + + @param: addrlist = either person or repository address list + @param: showsrc = True -- person and their sources + False -- repository with no sources + None -- Address Book address with sources + """ + if not addrlist: + return None + + # begin summaryarea division + with Html("div", id="AddressTable") as summaryarea: + + # begin address table + with Html("table") as table: + summaryarea += table + + # get table class based on showsrc + if showsrc is True: + table.attr = 'class = "infolist addrlist"' + elif showsrc is False: + table.attr = 'class = "infolist repolist"' + else: + table.attr = 'class = "infolist addressbook"' + + # begin table head + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + addr_header = [[self._("Date"), "Date"], + [self._("Street"), "StreetAddress"], + [self._("Locality"), "Locality"], + [self._("City"), "City"], + [self._("State/ Province"), "State"], + [self._("County"), "County"], + [self._("Postal Code"), "Postalcode"], + [self._("Country"), "Cntry"], + [self._("Phone"), "Phone"]] + + # True, False, or None ** see docstring for explanation + if showsrc in [True, None]: + addr_header.append([self._("Sources"), "Sources"]) + + trow.extend( + Html("th", self._(label), + class_="Colummn" + colclass, inline=True) + for (label, colclass) in addr_header + ) + + # begin table body + tbody = Html("tbody") + table += tbody + + # get address list from an object; either repository or person + for address in addrlist: + + trow = Html("tr") + tbody += trow + + addr_data_row = [ + (self.rlocale.get_date(address.get_date_object()), + "ColumnDate"), + (address.get_street(), "ColumnStreetAddress"), + (address.get_locality(), "ColumnLocality"), + (address.get_city(), "ColumnCity"), + (address.get_state(), "ColumnState"), + (address.get_county(), "ColumnCounty"), + (address.get_postal_code(), "ColumnPostalCode"), + (address.get_country(), "ColumnCntry"), + (address.get_phone(), "ColumnPhone") + ] + + # get source citation list + if showsrc in [True, None]: + addr_data_row.append( + [self.get_citation_links( + address.get_citation_list()), + "ColumnSources"]) + + trow.extend( + Html("td", value or " ", + class_=colclass, inline=True) + for (value, colclass) in addr_data_row + ) + + # address: notelist + if showsrc is not None: + notelist = self.display_note_list( + address.get_note_list()) + if notelist is not None: + summaryarea += notelist + return summaryarea + + def addressbook_link(self, person_handle, uplink=False): + """ + Creates a hyperlink for an address book link based on person's handle + + @param: person_handle -- Person's handle from the database + @param: uplink -- If True, then "../../../" is inserted in front + of the result. + """ + url = self.report.build_url_fname_html(person_handle, "addr", uplink) + person = self.r_db.get_person_from_handle(person_handle) + person_name = self.get_name(person) + + # return addressbook hyperlink to its caller + return Html("a", person_name, href=url, + title=html_escape(person_name)) + + def get_name(self, person, maiden_name=None): + """ I5118 + + Return person's name, unless maiden_name given, unless married_name + listed. + + @param: person -- person object from database + @param: maiden_name -- Female's family surname + """ + # get name format for displaying names + name_format = self.report.options['name_format'] + + # 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()) == 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 = Name(married_name) + else: + name = Name(primary_name) + surname_obj = name.get_primary_surname() + surname_obj.set_surname(maiden_name) + else: + name = Name(primary_name) + name.set_display_as(name_format) + return _nd.display_name(name) + + def display_attribute_header(self): + """ + Display the attribute section and its table header + """ + # begin attributes division and section title + with Html("div", class_="subsection", id="attributes") as section: + section += Html("h4", self._("Attributes"), inline=True) + + # begin attributes table + with Html("table", class_="infolist attrlist") as attrtable: + section += attrtable + + thead = Html("thead") + attrtable += thead + + trow = Html("tr") + thead += trow + + trow.extend( + Html("th", label, class_=colclass, inline=True) + for (label, colclass) in [ + (self._("Type"), "ColumnType"), + (self._("Value"), "ColumnValue"), + (self._("Notes"), "ColumnNotes"), + (self._("Sources"), "ColumnSources")] + ) + return section, attrtable + + def display_attr_list(self, attrlist, + attrtable): + """ + Will display a list of attributes + + @param: attrlist -- a list of attributes + @param: attrtable -- the table element that is being added to + """ + tbody = Html("tbody") + attrtable += tbody + + tbody.extend( + self.dump_attribute(attr) for attr in attrlist + ) + + def write_footer(self, date): + """ + Will create and display the footer section of each page... + + @param: bottom -- whether to specify location of footer section or not? + """ + # begin footer division + with Html("div", id="footer") as footer: + + footer_note = self.report.options['footernote'] + if footer_note: + note = self.get_note_format( + self.r_db.get_note_from_gramps_id(footer_note), + False + ) + user_footer = Html("div", id='user_footer') + footer += user_footer + + # attach note + user_footer += note + + msg = self._('Generated by %(gramps_home_html_start)s' + 'Gramps%(html_end)s %(version)s' + ) % {'gramps_home_html_start' : + '', + 'html_end' : '', + 'version' : VERSION} + if date is not None: + msg += "
" + last_modif = datetime.datetime.fromtimestamp(date).strftime( + '%Y-%m-%d %H:%M:%S') + msg += self._('Last change was the %(date)s') % {'date' : + last_modif} + else: + dat_txt = ' on %(date)s' % {'date' : + self.rlocale.get_date(Today())} + msg += self._(dat_txt) + + origin1 = self.report.filter.get_name(self.rlocale) + filt_number = self.report.options['filter'] + # optional "link-home" feature; see bug report #2736 + if self.report.options['linkhome']: + center_person = self.r_db.get_person_from_gramps_id( + self.report.options['pid']) + if (center_person and + self.report.person_in_webreport(center_person.handle)): + center_person_url = self.report.build_url_fname_html( + center_person.handle, "ppl", self.uplink) + + #person_name = self.get_name(center_person) + if filt_number > 0 and filt_number < 5: + subject_url = '' + subject_url += origin1 + '' + else: + subject_url = origin1 + msg += self._( + '%(http_break)sCreated for %(subject_url)s') % { + 'http_break' : '
', + 'subject_url' : subject_url} + else: + msg += self._( + '%(http_break)sCreated for %(subject_url)s') % { + 'http_break' : '
', + 'subject_url' : origin1} + + # creation author + footer += Html("p", msg, id='createdate') + + # get copyright license for all pages + copy_nr = self.report.copyright + + text = '' + if copy_nr == 0: + if self.author: + year = Today().get_year() + text = '© %(year)d %(person)s' % { + 'person' : self.author, 'year' : year} + elif copy_nr < len(_CC): + # Note. This is a URL + fname = "/".join(["images", "somerights20.gif"]) + url = self.report.build_url_fname(fname, None, self.uplink) + text = _CC[copy_nr] % {'gif_fname' : url} + footer += Html("p", text, id='copyright') + + # return footer to its callers + return footer + + def write_header(self, title): + """ + Note. 'title' is used as currentsection in the navigation links and + as part of the header title. + + @param: title -- Is the title of the web page + """ + # begin each html page... + xmllang = xml_lang() + page, head, body = Html.page('%s - %s' % + (html_escape(self.title_str.strip()), + html_escape(title)), + self.report.encoding, + xmllang, cms=self.usecms) + + # temporary fix for .php parsing error + if self.ext in [".php", ".php3", ".cgi"]: + del page[0] + + # Header constants + _meta1 = 'name ="viewport" content="width=device-width; ' + _meta1 += 'height=device-height; initial-scale=0.1; ' + _meta1 += 'maximum-scale=10.0; user-scalable=yes"' + _meta2 = 'name ="apple-mobile-web-app-capable" content="yes"' + _meta3 = 'name="generator" content="%s %s %s"' % ( + PROGRAM_NAME, VERSION, URL_HOMEPAGE) + _meta4 = 'name="author" content="%s"' % self.author + + # create additional meta tags + meta = Html("meta", attr=_meta1) + ( + Html("meta", attr=_meta2, indent=False), + Html("meta", attr=_meta3, indent=False), + Html("meta", attr=_meta4, indent=False) + ) + + # Link to _NARRATIVESCREEN stylesheet + fname = "/".join(["css", _NARRATIVESCREEN]) + url2 = self.report.build_url_fname(fname, None, self.uplink) + + # Link to _NARRATIVEPRINT stylesheet + fname = "/".join(["css", _NARRATIVEPRINT]) + url3 = self.report.build_url_fname(fname, None, self.uplink) + + # Link to Gramps favicon + fname = "/".join(['images', 'favicon2.ico']) + url4 = self.report.build_url_image("favicon2.ico", + "images", self.uplink) + + # create stylesheet and favicon links + links = Html("link", type="image/x-icon", + href=url4, rel="shortcut icon") + ( + Html("link", type="text/css", href=url2, + media="screen", rel="stylesheet", indent=False), + Html("link", type="text/css", href=url3, + media='print', rel="stylesheet", indent=False) + ) + + # Link to Navigation Menus stylesheet + if CSS[self.report.css]["navigation"]: + fname = "/".join(["css", "narrative-menus.css"]) + url = self.report.build_url_fname(fname, None, self.uplink) + links += Html("link", type="text/css", href=url, + media="screen", rel="stylesheet", indent=False) + + # add additional meta and link tags + head += meta + head += links + + # begin header section + headerdiv = Html("div", id='header') + ( + Html("h1", html_escape(self.title_str), + id="SiteTitle", inline=True) + ) + body += headerdiv + + header_note = self.report.options['headernote'] + if header_note: + note = self.get_note_format( + self.r_db.get_note_from_gramps_id(header_note), + False) + + user_header = Html("div", id='user_header') + headerdiv += user_header + + # attach note + user_header += note + + # Begin Navigation Menu-- + # is the style sheet either Basic-Blue or Visually Impaired, + # and menu layout is Drop Down? + if (self.report.css == _("Basic-Blue") or + self.report.css == _("Visually Impaired") + ) and self.report.navigation == "dropdown": + body += self.display_drop_menu() + else: + body += self.display_nav_links(title) + + # return page, head, and body to its classes... + return page, head, body + + def display_nav_links(self, currentsection): + """ + Creates the navigation menu + + @param: currentsection = which menu item are you on + """ + # include repositories or not? + inc_repos = True + if (not self.report.inc_repository or + not len(self.r_db.get_repository_handles())): + inc_repos = False + + # create media pages... + _create_media_link = False + if self.create_media: + _create_media_link = True + if self.create_thumbs_only: + _create_media_link = False + + # create link to web calendar pages... + #_create_calendar_link = False + if self.usecal: + #_create_calendar_link = True + self.target_cal_uri += "/index" + + # Determine which menu items will be available? + # Menu items have been adjusted to concide with Gramps Navigation + # Sidebar order... + + navs = [ + (self.report.index_fname, self._("Html|Home"), + self.report.use_home), + (self.report.intro_fname, self._("Introduction"), + self.report.use_intro), + ('individuals', self._("Individuals"), True), + (self.report.surname_fname, self._("Surnames"), True), + ('families', self._("Families"), self.report.inc_families), + ('events', self._("Events"), self.report.inc_events), + ('places', self._("Places"), True), + ('sources', self._("Sources"), True), + ('repositories', self._("Repositories"), inc_repos), + ('media', self._("Media"), _create_media_link), + ('thumbnails', self._("Thumbnails"), self.create_media), + ('download', self._("Download"), self.report.inc_download), + ("addressbook", self._("Address Book"), + self.report.inc_addressbook), + ('contact', self._("Contact"), self.report.use_contact), + ('statistics', self._("Statistics"), True), + (self.target_cal_uri, self._("Web Calendar"), self.usecal) + ] + + # Remove menu sections if they are not being created? + navs = ((url_text, nav_text) + for url_text, nav_text, cond in navs if cond) + menu_items = [[url, text] for url, text in navs] + + number_items = len(menu_items) + num_cols = 10 + num_rows = ((number_items // num_cols) + 1) + + # begin navigation menu division... + with Html("div", class_="wrapper", + id="nav", role="navigation") as navigation: + with Html("div", class_="container") as container: + + index = 0 + for rows in range(num_rows): + unordered = Html("ul", class_="menu", id="dropmenu") + + cols = 0 + while cols <= num_cols and index < number_items: + url_fname, nav_text = menu_items[index] + + hyper = self.get_nav_menu_hyperlink(url_fname, nav_text) + + # Define 'currentsection' to correctly set navlink item + # CSS id 'CurrentSection' for Navigation styling. + # Use 'self.report.cur_fname' to determine + # 'CurrentSection' for individual elements for + # Navigation styling. + + # Figure out if we need
  • + # or just
  • + + check_cs = False + if nav_text == currentsection: + check_cs = True + elif nav_text == _("Surnames"): + if "srn" in self.report.cur_fname: + check_cs = True + elif _("Surnames") in currentsection: + check_cs = True + elif nav_text == _("Individuals"): + if "ppl" in self.report.cur_fname: + check_cs = True + elif nav_text == _("Families"): + if "fam" in self.report.cur_fname: + check_cs = True + elif nav_text == _("Sources"): + if "src" in self.report.cur_fname: + check_cs = True + elif nav_text == _("Places"): + if "plc" in self.report.cur_fname: + check_cs = True + elif nav_text == _("Events"): + if "evt" in self.report.cur_fname: + check_cs = True + elif nav_text == _("Media"): + if "img" in self.report.cur_fname: + check_cs = True + elif nav_text == _("Address Book"): + if "addr" in self.report.cur_fname: + check_cs = True + temp_cs = 'class = "CurrentSection"' + check_cs = temp_cs if check_cs else False + if check_cs: + unordered.extend( + Html("li", hyper, attr=check_cs, inline=True) + ) + else: + unordered.extend( + Html("li", hyper, inline=True) + ) + index += 1 + cols += 1 + + if rows == num_rows - 1: + prv = Html('%s' % + self._("Previous")) + nxt = Html('%s' % + self._("Next")) + unordered.extend(Html("li", prv, inline=True)) + unordered.extend(Html("li", nxt, inline=True)) + container += unordered + navigation += container + return navigation + + def display_drop_menu(self): + """ + Creates the Drop Down Navigation Menu + """ + # include repositories or not? + inc_repos = True + if (not self.report.inc_repository or + not len(self.r_db.get_repository_handles())): + inc_repos = False + + # create media pages... + _create_media_link = False + if self.create_media: + _create_media_link = True + if self.create_thumbs_only: + _create_media_link = False + + personal = [ + (self.report.intro_fname, self._("Introduction"), + self.report.use_intro), + ("individuals", self._("Individuals"), True), + (self.report.surname_fname, self._("Surnames"), True), + ("families", self._("Families"), self.report.inc_families) + ] + personal = ((url_text, nav_text) + for url_text, nav_text, cond in personal if cond) + personal = [[url, text] for url, text in personal] + + navs1 = [ + ("events", self._("Events"), self.report.inc_events), + ("places", self._("Places"), True), + ("sources", self._("Sources"), True), + ("repositories", self._("Repositories"), inc_repos) + ] + navs1 = ((url_text, nav_text) + for url_text, nav_text, cond in navs1 if cond) + navs1 = [[url, text] for url, text in navs1] + + media = [ + ("media", self._("Media"), _create_media_link), + ("thumbnails", self._("Thumbnails"), True) + ] + media = ((url_text, nav_text) + for url_text, nav_text, cond in media if cond) + media = [[url, text] for url, text in media] + + misc = [ + ('download', self._("Download"), self.report.inc_download), + ("addressbook", self._("Address Book"), self.report.inc_addressbook) + ] + misc = ((url_text, nav_text) + for url_text, nav_text, cond in misc if cond) + misc = [[url, text] for url, text in misc] + + contact = [ + ('contact', self._("Contact"), self.report.use_contact) + ] + contact = ((url_text, nav_text) + for url_text, nav_text, cond in contact if cond) + contact = [[url, text] for url, text in contact] + + # begin navigation menu division... + with Html("div", class_="wrapper", + id="nav", role="navigation") as navigation: + with Html("div", class_="container") as container: + unordered = Html("ul", class_="menu", id="dropmenu") + + if self.report.use_home: + list_html = Html("li", + self.get_nav_menu_hyperlink( + self.report.index_fname, + self._("Html|Home"))) + unordered += list_html + + # add personal column + self.get_column_data(unordered, personal, self._("Personal")) + + if len(navs1): + for url_fname, nav_text in navs1: + unordered.extend( + Html("li", self.get_nav_menu_hyperlink(url_fname, + nav_text), + inline=True) + ) + + # add media column + self.get_column_data(unordered, media, self._("Media")) + + # add miscellaneous column + self.get_column_data(unordered, misc, self._("Miscellaneous")) + + # add contact column + self.get_column_data(unordered, contact, _("Contact")) + + container += unordered + navigation += container + return navigation + + def add_image(self, option_name, height=0): + """ + Will add an image (if present) to the page + + @param: option_name -- The name of the report option + @param: height -- Height of the image + """ + pic_id = self.report.options[option_name] + if pic_id: + obj = self.r_db.get_media_from_gramps_id(pic_id) + if obj is None: + return None + mime_type = obj.get_mime_type() + if mime_type and mime_type.startswith("image"): + try: + + newpath, thumb_path = self.report.prepare_copy_media(obj) + self.report.copy_file(media_path_full( + self.r_db, obj.get_path()), newpath) + + # begin image + image = Html("img") + image.attr = '' + if height: + image.attr += 'height = "%d"' % height + + descr = html_escape(obj.get_description()) + newpath = self.report.build_url_fname(newpath) + image.attr += ' src = "%s" alt = "%s"' % (newpath, descr) + + # return an image + return image + + except (IOError, OSError) as msg: + self.r_user.warn(_("Could not add photo to page"), + str(msg)) + + # no image to return + return None + + def media_ref_rect_regions(self, handle): + """ + 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 + + @param: handle -- The media handle to use + """ + # 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 self.r_db.find_backlink_handles( + handle, + include_classes=["Person", "Family", "Event", "Place"]): + + # 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": + # Is this a person for whom we have built a page: + if self.report.person_in_webreport(newhandle): + # If so, let's add a link to them: + _obj = self.r_db.get_person_from_handle(newhandle) + if _obj: + # What is the shortest possible name we could use + # for this person? + _name = (_obj.get_primary_name().get_call_name() or + _obj.get_primary_name().get_first_name() or + self._("Unknown") + ) + _linkurl = self.report.build_url_fname_html(_obj.handle, + "ppl", True) + elif classname == "Family": + _obj = self.r_db.get_family_from_handle(newhandle) + partner1_handle = _obj.get_father_handle() + partner2_handle = _obj.get_mother_handle() + partner1 = None + partner2 = None + if partner1_handle: + partner1 = self.r_db.get_person_from_handle( + partner1_handle) + if partner2_handle: + partner2 = self.r_db.get_person_from_handle( + partner2_handle) + if partner2 and partner1: + _name = partner1.get_primary_name().get_first_name() + _linkurl = self.report.build_url_fname_html(partner1_handle, + "ppl", True) + elif partner1: + _name = partner1.get_primary_name().get_first_name() + _linkurl = self.report.build_url_fname_html(partner1_handle, + "ppl", True) + elif partner2: + _name = partner2.get_primary_name().get_first_name() + _linkurl = self.report.build_url_fname_html(partner2_handle, + "ppl", True) + if not _name: + _name = self._("Unknown") + elif classname == "Event": + _obj = self.r_db.get_event_from_handle(newhandle) + _name = _obj.get_description() + if not _name: + _name = self._("Unknown") + _linkurl = self.report.build_url_fname_html(_obj.handle, + "evt", True) + elif classname == "Place": + _obj = self.r_db.get_place_from_handle(newhandle) + _name = _pd.display(self.r_db, _obj) + if not _name: + _name = self._("Unknown") + _linkurl = self.report.build_url_fname_html(newhandle, + "plc", True) + + # continue looking through the loop for an object... + if _obj is None: + continue + + # get a list of all media refs for this object + media_list = _obj.get_media_list() + + # go media refs looking for one that points to this image + for mediaref in media_list: + + # is this mediaref for this image? do we have a rect? + if mediaref.ref == handle and mediaref.rect is not None: + + (coord_x1, coord_y1, coord_x2, coord_y2) = mediaref.rect + # Gramps gives us absolute coordinates, + # but we need relative width + height + width = coord_x2 - coord_x1 + height = coord_y2 - coord_y1 + + # remember all this information, cause we'll need + # need it later when we output the
  • ...
  • tags + item = (_name, coord_x1, coord_y1, width, height, _linkurl) + _region_items.add(item) + + # End of code that looks for and prepares the media object regions + + return sorted(_region_items) + + def media_ref_region_to_object(self, media_handle, obj): + """ + Return a region of this image if it refers to this object. + + @param: media_handle -- The media handle to use + @param: obj -- The object reference + """ + # get a list of all media refs for this object + for mediaref in obj.get_media_list(): + # is this mediaref for this image? do we have a rect? + if (mediaref.ref == media_handle and + mediaref.rect is not None): + return mediaref.rect # (x1, y1, x2, y2) + return None + + def disp_first_img_as_thumbnail(self, photolist, object_): + """ + Return the Html of the first image of photolist that is + associated with object. First image might be a region in an + image. Or, the first image might have regions defined in it. + + @param: photolist -- The list of media + @param: object_ -- The object reference + """ + if not photolist or not self.create_media: + return None + + photo_handle = photolist[0].get_reference_handle() + photo = self.r_db.get_media_from_handle(photo_handle) + mime_type = photo.get_mime_type() + descr = photo.get_description() + + # begin snapshot division + with Html("div", class_="snapshot") as snapshot: + + if mime_type: + + region = self.media_ref_region_to_object(photo_handle, object_) + if region: + + # make a thumbnail of this region + newpath = self.copy_thumbnail(photo_handle, photo, region) + newpath = self.report.build_url_fname(newpath, uplink=True) + + snapshot += self.media_link(photo_handle, newpath, descr, + uplink=self.uplink, + usedescr=False) + else: + + real_path, newpath = self.report.prepare_copy_media(photo) + newpath = self.report.build_url_fname(newpath, uplink=True) + + # FIXME: There doesn't seem to be any point in highlighting + # a sub-region in the thumbnail and linking back to the + # person or whatever. First it is confusing when the link + # probably has nothing to do with the page on which the + # thumbnail is displayed, and second on a thumbnail it is + # probably too small to see, and third, on the thumbnail, + # the link is shown above the image (which is pretty + # useless!) + _region_items = self.media_ref_rect_regions(photo_handle) + if len(_region_items): + with Html("div", id="GalleryDisplay") as mediadisplay: + snapshot += mediadisplay + + ordered = Html("ol", class_="RegionBox") + mediadisplay += ordered + while len(_region_items): + (name, coord_x, coord_y, + width, height, linkurl) = _region_items.pop() + ordered += Html("li", + style="left:%d%%; top:%d%%; " + "width:%d%%; height:%d%%;" % ( + coord_x, coord_y, + width, height)) + ordered += Html("a", name, href=linkurl) + # Need to add link to mediadisplay to get the links: + mediadisplay += self.media_link(photo_handle, + newpath, descr, + self.uplink, False) + else: + try: + + # Begin hyperlink. Description is given only for + # the purpose of the alt tag in img element + snapshot += self.media_link(photo_handle, newpath, + descr, + uplink=self.uplink, + usedescr=False) + + except (IOError, OSError) as msg: + self.r_user.warn(_("Could not add photo to page"), + str(msg)) + else: + # begin hyperlink + snapshot += self.doc_link(photo_handle, descr, + uplink=self.uplink, usedescr=False) + + # return snapshot division to its callers + return snapshot + + def disp_add_img_as_gallery(self, photolist, object_): + """ + Display additional image as gallery + + @param: photolist -- The list of media + @param: object_ -- The object reference + """ + if not photolist or not self.create_media: + return None + + # make referenced images have the same order as in media list: + photolist_handles = {} + for mediaref in photolist: + photolist_handles[mediaref.get_reference_handle()] = mediaref + photolist_ordered = [] + for photoref in copy.copy(object_.get_media_list()): + if photoref.ref in photolist_handles: + photo = photolist_handles[photoref.ref] + photolist_ordered.append(photo) + try: + photolist.remove(photo) + except ValueError: + LOG.warning("Error trying to remove '%s' from photolist", + photo) + # and add any that are left (should there be any?) + photolist_ordered += photolist + + # begin individualgallery division and section title + with Html("div", class_="subsection", id="indivgallery") as section: + section += Html("h4", self._("Media"), inline=True) + + displayed = [] + for mediaref in photolist_ordered: + + photo_handle = mediaref.get_reference_handle() + photo = self.r_db.get_media_from_handle(photo_handle) + + if photo_handle in displayed: + continue + mime_type = photo.get_mime_type() + + # get media description + descr = photo.get_description() + + if mime_type: + try: + # create thumbnail url + # extension needs to be added as it is not already there + url = self.report.build_url_fname(photo_handle, "thumb", + True) + ".png" + # begin hyperlink + section += self.media_link(photo_handle, url, + descr, uplink=self.uplink, + usedescr=True) + except (IOError, OSError) as msg: + self.r_user.warn(_("Could not add photo to page"), + str(msg)) + else: + try: + # begin hyperlink + section += self.doc_link(photo_handle, descr, + uplink=self.uplink) + except (IOError, OSError) as msg: + self.r_user.warn(_("Could not add photo to page"), + str(msg)) + displayed.append(photo_handle) + + # add fullclear for proper styling + section += FULLCLEAR + + # return indivgallery division to its caller + return section + + def display_note_list(self, notelist=None): + """ + Display note list + + @param: notelist -- The list of notes + """ + if not notelist: + return None + + # begin narrative division + with Html("div", class_="subsection narrative") as section: + + for notehandle in notelist: + note = self.r_db.get_note_from_handle(notehandle) + + if note: + note_text = self.get_note_format(note, True) + + # add section title + section += Html("h4", self._("Narrative"), inline=True) + + # attach note + section += note_text + + # return notes to its callers + return section + + def display_url_list(self, urllist=None): + """ + Display URL list + + @param: urllist -- The list of urls + """ + if not urllist: + return None + + # begin web links division + with Html("div", class_="subsection", id="WebLinks") as section: + section += Html("h4", self._("Web Links"), inline=True) + + with Html("table", class_="infolist weblinks") as table: + section += table + + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + trow.extend(Html('th', label, class_=colclass, inline=True) + for (label, colclass) in [ + (self._("Type"), "ColumnType"), + (self._("Description"), "ColumnDescription")] + ) + + tbody = Html("tbody") + table += tbody + + for url in urllist: + + trow = Html("tr") + tbody += trow + + _type = self._(url.get_type().xml_str()) + uri = url.get_path() + descr = url.get_description() + + # Email address + if _type == UrlType.EMAIL: + if not uri.startswith("mailto:"): + uri = "mailto:%(email)s" % {'email' : uri} + + # Web Site address + elif _type == UrlType.WEB_HOME: + if not (uri.startswith("http://") or + uri.startswith("https://")): + url = self.secure_mode + uri = url + "%(website)s" % {"website" : uri} + + # FTP server address + elif _type == UrlType.WEB_FTP: + if not (uri.startswith("ftp://") or + uri.startswith("ftps://")): + uri = "ftp://%(ftpsite)s" % {"ftpsite" : uri} + + descr = Html("p", html_escape(descr)) + ( + Html("a", self._(" [Click to Go]"), href=uri, title=uri) + ) + + trow.extend( + Html("td", data, class_=colclass, inline=True) + for (data, colclass) in [ + (str(_type), "ColumnType"), + (descr, "ColumnDescription") + ] + ) + return section + + def display_lds_ordinance(self, db_obj_): + """ + Display LDS information for a person or family + + @param: db_obj_ -- The database object + """ + ldsordlist = db_obj_.lds_ord_list + if not ldsordlist: + return None + + # begin LDS Ordinance division and section title + with Html("div", class_="subsection", id="LDSOrdinance") as section: + section += Html("h4", _("Latter-Day Saints/ LDS Ordinance"), + inline=True) + + # ump individual LDS ordinance list + section += self.dump_ordinance(db_obj_, "Person") + + # return section to its caller + return section + + def display_ind_sources(self, srcobj): + """ + Will create the "Source References" section for an object + + @param: srcobj -- Sources object + """ + list(map( + lambda i: self.bibli.add_reference( + self.r_db.get_citation_from_handle(i)), + srcobj.get_citation_list())) + sourcerefs = self.display_source_refs(self.bibli) + + # return to its callers + return sourcerefs + + # Only used in IndividualPage.display_ind_sources(), + # and MediaPage.display_media_sources() + def display_source_refs(self, bibli): + """ + Display source references + + @param: bibli -- List of sources + """ + if bibli.get_citation_count() == 0: + return None + + with Html("div", class_="subsection", id="sourcerefs") as section: + section += Html("h4", self._("Source References"), inline=True) + + ordered = Html("ol") + + cindex = 0 + citationlist = bibli.get_citation_list() + for citation in citationlist: + cindex += 1 + # Add this source and its references to the page + source = self.r_db.get_source_from_handle( + citation.get_source_handle()) + if source is not None: + if source.get_author(): + authorstring = source.get_author() + ": " + else: + authorstring = "" + list_html = Html("li", + self.source_link( + source.get_handle(), + authorstring + source.get_title(), + source.get_gramps_id(), cindex, + uplink=self.uplink)) + else: + list_html = Html("li", "None") + + ordered1 = Html("ol") + citation_ref_list = citation.get_ref_list() + for key, sref in citation_ref_list: + cit_ref_li = Html("li", id="sref%d%s" % (cindex, key)) + tmp = Html("ul") + conf = conf_strings.get(sref.confidence, self._('Unknown')) + if conf == conf_strings[Citation.CONF_NORMAL]: + conf = None + else: + conf = _(conf) + for (label, data) in [[self._("Date"), + self.rlocale.get_date(sref.date)], + [self._("Page"), sref.page], + [self._("Confidence"), conf]]: + if data: + tmp += Html("li", + _("%(str1)s: %(str2)s") % { + 'str1' : label, + 'str2' : data + }) + if self.create_media: + for media_ref in sref.get_media_list(): + media_handle = media_ref.get_reference_handle() + media = self.r_db.get_media_from_handle( + media_handle) + if media: + mime_type = media.get_mime_type() + if mime_type: + if mime_type.startswith("image/"): + real_path, newpath = \ + self.report.prepare_copy_media( + media) + newpath = self.report.build_url_fname( + newpath, uplink=self.uplink) + dest_dir = os.path.dirname( + self.report.cur_fname) + if dest_dir: + newpath = os.path.join(dest_dir, + newpath) + self.report.copy_file( + media_path_full(self.r_db, + media.get_path()), + newpath) + + tmp += Html("li", + self.media_link( + media_handle, + newpath, + media.get_description(), + self.uplink, + usedescr=False), + inline=True) + + else: + tmp += Html("li", + self.doc_link( + media_handle, + media.get_description(), + self.uplink, + usedescr=False), + inline=True) + for handle in sref.get_note_list(): + this_note = self.r_db.get_note_from_handle(handle) + if this_note is not None: + note_format = self.get_note_format(this_note, True) + tmp += Html("li", + _("%(str1)s: %(str2)s") % { + 'str1' : str(this_note.get_type()), + 'str2' : note_format + }) + if tmp: + cit_ref_li += tmp + ordered1 += cit_ref_li + + if citation_ref_list: + list_html += ordered1 + ordered += list_html + section += ordered + + # return section to its caller + return section + + def display_references(self, handlelist, + uplink=False): + """ + Display references for the current objects + + @param: handlelist -- List of handles + @param: uplink -- If True, then "../../../" is inserted in front of + the result. + """ + if not handlelist: + return None + + # begin references division and title + with Html("div", class_="subsection", id="references") as section: + section += Html("h4", self._("References"), inline=True) + + ordered = Html("ol") + section += ordered + sortlist = sorted(handlelist, + key=lambda x: self.rlocale.sort_key(x[1])) + + for (path, name, gid) in sortlist: + list_html = Html("li") + ordered += list_html + + name = name or self._("Unknown") + if not self.noid and gid != "": + gid_html = Html("span", " [%s]" % gid, class_="grampsid", + inline=True) + else: + gid_html = "" # pylint: disable=redefined-variable-type + + if path != "": + url = self.report.build_url_fname(path, None, self.uplink) + list_html += Html("a", href=url) + name + gid_html + else: + list_html += name + str(gid_html) + + # return references division to its caller + return section + + def family_map_link(self, handle, url): + """ + Creates a link to the family map + + @param: handle -- The family handle + @param: url -- url to be linked + """ + return Html("a", self._("Family Map"), href=url, + title=self._("Family Map"), class_="familymap", inline=True) + + def display_spouse(self, partner, family, place_lat_long): + """ + Display an individual's partner + + @param: partner -- The partner + @param: family -- The family + @param: place_lat_long -- For use in Family Map Pages. This will be None + if called from Family pages, which do not + create a Family Map + """ + gender = partner.get_gender() + reltype = family.get_relationship() + + rtype = self._(str(family.get_relationship().xml_str())) + + if reltype == FamilyRelType.MARRIED: + if gender == Person.FEMALE: + relstr = self._("Wife") + elif gender == Person.MALE: + relstr = self._("Husband") + else: + relstr = self._("Partner") + else: + relstr = self._("Partner") + + # display family relationship status, and add spouse to FamilyMapPages + if self.familymappages: + self._get_event_place(partner, place_lat_long) + + trow = Html("tr", class_="BeginFamily") + ( + Html("td", rtype, class_="ColumnType", inline=True), + Html("td", relstr, class_="ColumnAttribute", inline=True) + ) + + tcell = Html("td", class_="ColumnValue") + trow += tcell + + tcell += self.new_person_link(partner.get_handle(), uplink=True, + person=partner) + birth = death = "" + bd_event = get_birth_or_fallback(self.r_db, partner) + if bd_event: + birth = self.rlocale.get_date(bd_event.get_date_object()) + dd_event = get_death_or_fallback(self.r_db, partner) + if dd_event: + death = self.rlocale.get_date(dd_event.get_date_object()) + + if death == "": + death = "..." + tcell += " ( * ", birth, " + ", death, " )" + + return trow + + def display_child_link(self, chandle): + """ + display child link ... + + @param: chandle -- Child handle + """ + return self.new_person_link(chandle, uplink=True) + + def new_person_link(self, person_handle, uplink=False, person=None, + name_style=_NAME_STYLE_DEFAULT): + """ + creates a link for a person. If a page is generated for the person, a + hyperlink is created, else just the name of the person. The returned + vale will be an Html object if a hyperlink is generated, otherwise just + a string + + @param: person_handle -- Person in database + @param: uplink -- If True, then "../../../" is inserted in front + of the result + @param: person -- Person object. This does not need to be passed. + It should be passed if the person object has + already been retrieved, as it will be used to + improve performance + """ + result = self.report.obj_dict.get(Person).get(person_handle) + + # construct link, name and gid + if result is None: + # The person is not included in the webreport + link = "" + if person is None: + person = self.r_db.get_person_from_handle(person_handle) + if person: + name = self.report.get_person_name(person) + gid = person.get_gramps_id() + else: + name = _("Unknown") + gid = "" + else: + # The person has been encountered in the web report, but this does + # not necessarily mean that a page has been generated + (link, name, gid) = result + + if name_style == _NAME_STYLE_FIRST and person: + name = _get_short_name(person.get_gender(), + person.get_primary_name()) + name = html_escape(name) + # construct the result + if not self.noid and gid != "": + gid_html = Html("span", " [%s]" % gid, class_="grampsid", + inline=True) + else: + gid_html = "" # pylint: disable=redefined-variable-type + + if link != "": + url = self.report.build_url_fname(link, uplink=uplink) + hyper = Html("a", name, gid_html, href=url, inline=True) + else: + hyper = name + str(gid_html) + + return hyper + + def media_link(self, media_handle, img_url, name, + uplink=False, usedescr=True): + """ + creates and returns a hyperlink to the thumbnail image + + @param: media_handle -- Photo handle from report database + @param: img_url -- Thumbnail url + @param: name -- Photo description + @param: uplink -- If True, then "../../../" is inserted in front + of the result. + @param: usedescr -- Add media description + """ + url = self.report.build_url_fname_html(media_handle, "img", uplink) + name = html_escape(name) + + # begin thumbnail division + with Html("div", class_="thumbnail") as thumbnail: + + # begin hyperlink + if not self.create_thumbs_only: + hyper = Html("a", href=url, title=name) + ( + Html("img", src=img_url, alt=name) + ) + else: + hyper = Html("img", src=img_url, alt=name) + thumbnail += hyper + + if usedescr: + hyper += Html("p", name, inline=True) + return thumbnail + + def doc_link(self, handle, name, uplink=False, usedescr=True): + """ + create a hyperlink for the media object and returns it + + @param: handle -- Document handle + @param: name -- Document name + @param: uplink -- If True, then "../../../" is inserted in front of + the result. + @param: usedescr -- Add description to hyperlink + """ + url = self.report.build_url_fname_html(handle, "img", uplink) + name = html_escape(name) + + # begin thumbnail division + with Html("div", class_="thumbnail") as thumbnail: + document_url = self.report.build_url_image("document.png", + "images", uplink) + + if not self.create_thumbs_only: + document_link = Html("a", href=url, title=name) + ( + Html("img", src=document_url, alt=name) + ) + else: + document_link = Html("img", src=document_url, alt=name) + + if usedescr: + document_link += Html('br') + ( + Html("span", name, inline=True) + ) + thumbnail += document_link + return thumbnail + + def place_link(self, handle, name, gid=None, uplink=False): + """ + Returns a hyperlink for place link + + @param: handle -- repository handle from report database + @param: name -- repository title + @param: gid -- gramps id + @param: uplink -- If True, then "../../../" is inserted in front of the + result. + """ + url = self.report.build_url_fname_html(handle, "plc", uplink) + + hyper = Html("a", html_escape(name), href=url, + title=html_escape(name)) + if not self.noid and gid: + hyper += Html("span", " [%s]" % gid, class_="grampsid", inline=True) + + # return hyperlink to its callers + return hyper + + def dump_place(self, place, table): + """ + Dump a place's information from within the database + + @param: place -- Place object from the database + @param: table -- Table from Placedetail + """ + if place in self.report.visited: + return + self.report.visited.append(place) + # add table body + tbody = Html("tbody") + table += tbody + + gid = place.gramps_id + if not self.noid and gid: + trow = Html("tr") + ( + Html("td", self._("Gramps ID"), class_="ColumnAttribute", + inline=True), + Html("td", gid, class_="ColumnValue", inline=True) + ) + tbody += trow + + data = place.get_latitude() + if data != "": + trow = Html('tr') + ( + Html("td", self._("Latitude"), class_="ColumnAttribute", + inline=True), + Html("td", data, class_="ColumnValue", inline=True) + ) + tbody += trow + data = place.get_longitude() + if data != "": + trow = Html('tr') + ( + Html("td", self._("Longitude"), class_="ColumnAttribute", + inline=True), + Html("td", data, class_="ColumnValue", inline=True) + ) + tbody += trow + + mlocation = get_main_location(self.r_db, place) + for (label, data) in [ + (self._("Street"), mlocation.get(PlaceType.STREET, '')), + (self._("Locality"), mlocation.get(PlaceType.LOCALITY, '')), + (self._("City"), mlocation.get(PlaceType.CITY, '')), + (self._("Church Parish"), + mlocation.get(PlaceType.PARISH, '')), + (self._("County"), mlocation.get(PlaceType.COUNTY, '')), + (self._("State/ Province"), + mlocation.get(PlaceType.STATE, '')), + (self._("Postal Code"), place.get_code()), + (self._("Country"), mlocation.get(PlaceType.COUNTRY, ''))]: + if data: + trow = Html("tr") + ( + Html("td", label, class_="ColumnAttribute", inline=True), + Html("td", data, class_="ColumnValue", inline=True) + ) + tbody += trow + + altloc = place.get_alternate_locations() + if altloc: + tbody += Html("tr") + Html("td", " ", colspan=2) + trow = Html("tr") + ( + Html("th", self._("Alternate Locations"), colspan=2, + class_="ColumnAttribute", inline=True), + ) + tbody += trow + for loc in (nonempt + for nonempt in altloc if not nonempt.is_empty()): + for (label, data) in [(self._("Street"), loc.street), + (self._("Locality"), loc.locality), + (self._("City"), loc.city), + (self._("Church Parish"), loc.parish), + (self._("County"), loc.county), + (self._("State/ Province"), loc.state), + (self._("Postal Code"), loc.postal), + (self._("Country"), loc.country),]: + if data: + trow = Html("tr") + ( + Html("td", label, class_="ColumnAttribute", + inline=True), + Html("td", data, class_="ColumnValue", inline=True) + ) + tbody += trow + tbody += Html("tr") + Html("td", " ", colspan=2) + + # display all related locations + for placeref in place.get_placeref_list(): + place_date = self.rlocale.get_date(placeref.get_date_object()) + if place_date != "": + parent_place = self.r_db.get_place_from_handle(placeref.ref) + parent_name = parent_place.get_name().get_value() + trow = Html('tr') + ( + Html("td", self._("Locations"), class_="ColumnAttribute", + inline=True), + Html("td", parent_name, class_="ColumnValue", inline=True), + Html("td", place_date, class_="ColumnValue", inline=True) + ) + tbody += trow + + # return place table to its callers + return table + + def repository_link(self, repository_handle, name, gid=None, uplink=False): + """ + Returns a hyperlink for repository links + + @param: repository_handle -- repository handle from report database + @param: name -- repository title + @param: gid -- gramps id + @param: uplink -- If True, then "../../../" is inserted in + front of the result. + """ + url = self.report.build_url_fname_html(repository_handle, + 'repo', uplink) + name = html_escape(name) + + hyper = Html("a", name, href=url, title=name) + + if not self.noid and gid: + hyper += Html("span", '[%s]' % gid, class_="grampsid", inline=True) + return hyper + + def dump_repository_ref_list(self, repo_ref_list): + """ + Dumps the repository + + @param: repo_ref_list -- The list of repositories references + """ + if len(repo_ref_list) == 0: + return None + # Repository list division... + with Html("div", class_="subsection", + id="repositories") as repositories: + repositories += Html("h4", self._("Repositories"), inline=True) + + with Html("table", class_="infolist") as table: + repositories += table + + thead = Html("thead") + table += thead + + trow = Html("tr") + ( + Html("th", self._("Number"), class_="ColumnRowLabel", + inline=True), + Html("th", self._("Title"), class_="ColumnName", + inline=True), + Html("th", self._("Type"), class_="ColumnName", + inline=True), + Html("th", self._("Call number"), class_="ColumnName", + inline=True) + ) + thead += trow + + tbody = Html("tbody") + table += tbody + + index = 1 + for repo_ref in repo_ref_list: + repo = self.r_db.get_repository_from_handle(repo_ref.ref) + if repo: + trow = Html("tr") + ( + Html("td", index, class_="ColumnRowLabel", + inline=True), + Html("td", + self.repository_link(repo_ref.ref, + repo.get_name(), + repo.get_gramps_id(), + self.uplink)), + Html("td", + self._(repo_ref.get_media_type().xml_str()), + class_="ColumnName"), + Html("td", repo_ref.get_call_number(), + class_="ColumnName") + ) + tbody += trow + index += 1 + return repositories + + def dump_residence(self, has_res): + """ + Creates a residence from the database + + @param: has_res -- The residence to use + """ + if not has_res: + return None + + # begin residence division + with Html("div", class_="content Residence") as residence: + residence += Html("h4", self._("Residence"), inline=True) + + with Html("table", class_="infolist place") as table: + residence += table + + place_handle = has_res.get_place_handle() + if place_handle: + place = self.r_db.get_place_from_handle(place_handle) + if place: + self.dump_place(place, table) + + descr = has_res.get_description() + if descr: + + trow = Html("tr") + if len(table) == 3: + # append description row to tbody element of dump_place + table[-2] += trow + else: + # append description row to table element + table += trow + + trow.extend(Html("td", self._("Description"), + class_="ColumnAttribute", inline=True)) + trow.extend(Html("td", descr, class_="ColumnValue", + inline=True)) + + # return information to its callers + return residence + + def display_bkref(self, bkref_list, depth): + """ + Display a reference list for an object class + + @param: bkref_list -- The reference list + @param: depth -- The style of list to use + """ + list_style = "1", "a", "I", "A", "i" + ordered = Html("ol", class_="Col1", role="Volume-n-Page") + ordered.attr += " type=%s" % list_style[depth] + if depth > len(list_style): + return "" + # Sort by the role of the object at the bkref_class, bkref_handle + def sort_by_role(obj): + """ + Sort by role + """ + if obj[2] == "Primary": + role = "0" + elif obj[2] == "Family": + role = "1" + else: + role = "3" + return role + + for (bkref_class, bkref_handle, role) in sorted( + bkref_list, key=lambda x: + sort_by_role(x)): + list_html = Html("li") + path = self.report.obj_dict[bkref_class][bkref_handle][0] + name = self.report.obj_dict[bkref_class][bkref_handle][1] + gid = self.report.obj_dict[bkref_class][bkref_handle][2] + if role != "": + role = self._(" (%s) " % self._(role)) + ordered += list_html + if path == "": + list_html += name + list_html += self.display_bkref( + self.report.bkref_dict[bkref_class][bkref_handle], + depth+1) + else: + url = self.report.build_url_fname(path, uplink=self.uplink) + if not self.noid and gid != "": + gid_html = Html("span", " [%s]" % gid, + class_="grampsid", inline=True) + else: + gid_html = "" # pylint: disable=redefined-variable-type + list_html += Html("a", href=url) + name + role + gid_html + return ordered + + def display_bkref_list(self, obj_class, obj_handle): + """ + Display a reference list for an object class + + @param: obj_class -- The object class to use + @param: obj_handle -- The handle to use + """ + bkref_list = self.report.bkref_dict[obj_class][obj_handle] + if not bkref_list: + return None + # begin references division and title + with Html("div", class_="subsection", id="references") as section: + section += Html("h4", self._("References"), inline=True) + depth = 0 + ordered = self.display_bkref(bkref_list, depth) + section += ordered + return section + + # ------------------------------------------------------------------------- + # # Web Page Fortmatter and writer + # ------------------------------------------------------------------------- + def xhtml_writer(self, htmlinstance, output_file, sio, date): + """ + Will format, write, and close the file + + @param: output_file -- Open file that is being written to + @param: htmlinstance -- Web page created with libhtml + src/plugins/lib/libhtml.py + """ + htmlinstance.write(partial(print, file=output_file)) + + # closes the file + self.report.close_file(output_file, sio, date) + diff --git a/gramps/plugins/webreport/citation.py b/gramps/plugins/webreport/citation.py new file mode 100644 index 000000000..2925637e7 --- /dev/null +++ b/gramps/plugins/webreport/citation.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + CitationPages - dummy +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +################################################# +# +# Passes citations through to the Sources page +# +################################################# +class CitationPages(BasePage): + """ + This class is responsible for displaying information about the 'Citation' + database objects. It passes this information to the 'Sources' tab. It is + told by the 'add_instances' call which 'Citation's to display. + """ + def __init__(self, report): + """ + @param: report -- The instance of the main report class for + this report + """ + BasePage.__init__(self, report, title="") + + def display_pages(self, title): + pass diff --git a/gramps/plugins/webreport/common.py b/gramps/plugins/webreport/common.py new file mode 100644 index 000000000..26bd9e1d8 --- /dev/null +++ b/gramps/plugins/webreport/common.py @@ -0,0 +1,863 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010-2017 Serge Noiraud +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +This module is used to share variables, enums and functions between all modules + +""" + +from unicodedata import normalize +from collections import defaultdict +from hashlib import md5 +import re +import logging +from xml.sax.saxutils import escape + +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.display.name import displayer as _nd +from gramps.gen.display.place import displayer as _pd +from gramps.gen.utils.db import get_death_or_fallback +from gramps.gen.lib import (EventType, Date) +from gramps.gen.plug import BasePluginManager +from gramps.plugins.lib.libgedcom import make_gedcom_date, DATE_QUALITY +from gramps.gen.plug.report import utils +from gramps.plugins.lib.libhtml import Html + +LOG = logging.getLogger(".NarrativeWeb") + +# define clear blank line for proper styling +FULLCLEAR = Html("div", class_="fullclear", inline=True) +# define all possible web page filename extensions +_WEB_EXT = ['.html', '.htm', '.shtml', '.php', '.php3', '.cgi'] +# used to select secured web site or not +HTTP = "http://" +HTTPS = "https://" + +GOOGLE_MAPS = 'https://maps.googleapis.com/maps/' +# javascript code for marker path +MARKER_PATH = """ + var marker_png = '%s' +""" + +# javascript code for Google's FamilyLinks... +FAMILYLINKS = """ + var tracelife = %s + + function initialize() { + var myLatLng = new google.maps.LatLng(%s, %s); + + var mapOptions = { + scaleControl: true, + panControl: true, + backgroundColor: '#000000', + zoom: %d, + center: myLatLng, + mapTypeId: google.maps.MapTypeId.ROADMAP + }; + var map = new google.maps.Map(document.getElementById("map_canvas"), + mapOptions); + + var flightPath = new google.maps.Polyline({ + path: tracelife, + strokeColor: "#FF0000", + strokeOpacity: 1.0, + strokeWeight: 2 + }); + + flightPath.setMap(map); + }""" + +# javascript for Google's Drop Markers... +DROPMASTERS = """ + var markers = []; + var iterator = 0; + + var tracelife = %s + var map; + var myLatLng = new google.maps.LatLng(%s, %s); + + function initialize() { + var mapOptions = { + scaleControl: true, + zoomControl: true, + zoom: %d, + mapTypeId: google.maps.MapTypeId.ROADMAP, + center: myLatLng, + }; + map = new google.maps.Map(document.getElementById("map_canvas"), + mapOptions); + }; + + function drop() { + for (var i = 0; i < tracelife.length; i++) { + setTimeout(function() { + addMarker(); + }, i * 1000); + } + } + + function addMarker() { + var location = tracelife[iterator]; + var myLatLng = new google.maps.LatLng(location[1], location[2]); + + markers.push(new google.maps.Marker({ + position: myLatLng, + map: map, + draggable: true, + title: location[0], + animation: google.maps.Animation.DROP + })); + iterator++; + }""" + +# javascript for Google's Markers... +MARKERS = """ + var tracelife = %s + var map; + var myLatLng = new google.maps.LatLng(%s, %s); + + function initialize() { + var mapOptions = { + scaleControl: true, + panControl: true, + backgroundColor: '#000000', + zoom: %d, + center: myLatLng, + mapTypeId: google.maps.MapTypeId.ROADMAP + }; + map = new google.maps.Map(document.getElementById("map_canvas"), + mapOptions); + addMarkers(); + } + + function addMarkers() { + var bounds = new google.maps.LatLngBounds(); + + for (var i = 0; i < tracelife.length; i++) { + var location = tracelife[i]; + var myLatLng = new google.maps.LatLng(location[1], location[2]); + + var marker = new google.maps.Marker({ + position: myLatLng, + draggable: true, + title: location[0], + map: map, + zIndex: location[3] + }); + bounds.extend(myLatLng); + if ( i > 1 ) { map.fitBounds(bounds); }; + } + }""" + +# javascript for OpenStreetMap's markers... +OSM_MARKERS = """ + function initialize(){ + var map; + var tracelife = %s; + var iconStyle = new ol.style.Style({ + image: new ol.style.Icon(({ + opacity: 1.0, + src: marker_png + })) + }); + var markerSource = new ol.source.Vector({ + }); + for (var i = 0; i < tracelife.length; i++) { + var loc = tracelife[i]; + var iconFeature = new ol.Feature({ + geometry: new ol.geom.Point(ol.proj.transform([loc[0], loc[1]], + 'EPSG:4326', 'EPSG:3857')), + name: loc[2], + }); + iconFeature.setStyle(iconStyle); + markerSource.addFeature(iconFeature); + } + markerLayer = new ol.layer.Vector({ + source: markerSource, + style: iconStyle + }); + var centerCoord = new ol.proj.transform([%s, %s], 'EPSG:4326', 'EPSG:3857'); + map= new ol.Map({ + target: 'map_canvas', + layers: [new ol.layer.Tile({ source: new ol.source.OSM() }), + markerLayer], + view: new ol.View({ center: centerCoord, zoom: %d }) + }); + var element = document.getElementById('popup'); + var tooltip = new ol.Overlay({ + element: element, + positioning: 'bottom-center', + stopEvent: false + }); + map.addOverlay(tooltip); + var displayFeatureInfo = function(pixel) { + var feature = map.forEachFeatureAtPixel(pixel, function(feature, layer) { + return feature; + }); + var info = document.getElementById('popup'); + if (feature) { + var geometry = feature.getGeometry(); + var coord = geometry.getCoordinates(); + tooltip.setPosition(coord); + $(element).siblings('.popover').css({ width: '250px' }); + $(element).siblings('.popover').css({ background: '#aaa' }); + $(info).popover({ + 'placement': 'auto', + 'html': true, + 'content': feature.get('name') + }); + $(info).popover('show'); + } else { + // TODO : some warning with firebug here + $(info).popover('destroy'); + $('.popover').remove(); + } + }; + map.on('pointermove', function(evt) { + if (evt.dragging) { + return; + } + var pixel = map.getEventPixel(evt.originalEvent); + displayFeatureInfo(pixel); + }); + map.on('click', function(evt) { + displayFeatureInfo(evt.pixel); + }); + }; +""" + +# variables for alphabet_navigation() +_KEYPERSON, _KEYPLACE, _KEYEVENT, _ALPHAEVENT = 0, 1, 2, 3 + +COLLATE_LANG = glocale.collation + +_NAME_STYLE_SHORT = 2 +_NAME_STYLE_DEFAULT = 1 +_NAME_STYLE_FIRST = 0 +_NAME_STYLE_SPECIAL = None + +PLUGMAN = BasePluginManager.get_instance() +CSS = PLUGMAN.process_plugin_data('WEBSTUFF') + +#_NAME_COL = 3 + +_WRONGMEDIAPATH = [] + +_HTML_DBL_QUOTES = re.compile(r'([^"]*) " ([^"]*) " (.*)', re.VERBOSE) +_HTML_SNG_QUOTES = re.compile(r"([^']*) ' ([^']*) ' (.*)", re.VERBOSE) + +# Events that are usually a family event +_EVENTMAP = set([EventType.MARRIAGE, EventType.MARR_ALT, + EventType.MARR_SETTL, EventType.MARR_LIC, + EventType.MARR_CONTR, EventType.MARR_BANNS, + EventType.ENGAGEMENT, EventType.DIVORCE, + EventType.DIV_FILING]) + +# Names for stylesheets +_NARRATIVESCREEN = "narrative-screen.css" +_NARRATIVEPRINT = "narrative-print.css" + +def sort_people(dbase, handle_list, rlocale=glocale): + """ + will sort the database people by surname + """ + sname_sub = defaultdict(list) + sortnames = {} + + for person_handle in handle_list: + person = dbase.get_person_from_handle(person_handle) + primary_name = person.get_primary_name() + + if primary_name.group_as: + surname = primary_name.group_as.encode('utf-8') + else: + group_map = _nd.primary_surname(primary_name) + surname = dbase.get_name_group_mapping(group_map) + if isinstance(surname, bytes): + surname = surname.decode('utf-8') + + # Treat people who have no name with those whose name is just + # 'whitespace' + if surname is None or surname.isspace(): + surname = '' + sortnames[person_handle] = _nd.sort_string(primary_name) + sname_sub[surname].append(person_handle) + + sorted_lists = [] + temp_list = sorted(sname_sub, key=rlocale.sort_key) + + for name in temp_list: + if isinstance(name, bytes): + name = name.decode('utf-8') + slist = sorted(((sortnames[x], x) for x in sname_sub[name]), + key=lambda x: rlocale.sort_key(x[0])) + entries = [x[1] for x in slist] + sorted_lists.append((name, entries)) + + return sorted_lists + +def sort_event_types(dbase, event_types, event_handle_list, rlocale): + """ + sort a list of event types and their associated event handles + + @param: dbase -- report database + @param: event_types -- a dict of event types + @param: event_handle_list -- all event handles in this database + """ + event_dict = dict((evt_type, list()) for evt_type in event_types) + + for event_handle in event_handle_list: + + event = dbase.get_event_from_handle(event_handle) + event_type = rlocale.translation.sgettext(event.get_type().xml_str()) + + # add (gramps_id, date, handle) from this event + if event_type in event_dict: + sort_value = event.get_date_object().get_sort_value() + event_dict[event_type].append((sort_value, event_handle)) + + for tup_list in event_dict.values(): + tup_list.sort() + + # return a list of sorted tuples, one per event + retval = [(event_type, event_list) for (event_type, + event_list) in event_dict.items()] + retval.sort(key=lambda item: str(item[0])) + + return retval + +# Modified _get_regular_surname from WebCal.py to get prefix, first name, +# and suffix +def _get_short_name(gender, name): + """ Will get suffix for all people passed through it """ + + short_name = name.get_first_name() + suffix = name.get_suffix() + if suffix: + short_name = short_name + ", " + suffix + return short_name + +def __get_person_keyname(dbase, handle): + """ .... """ + + person = dbase.get_person_from_handle(handle) + return _nd.sort_string(person.get_primary_name()) + +def __get_place_keyname(dbase, handle): + """ ... """ + + return utils.place_name(dbase, handle) + +# See : http://www.gramps-project.org/bugs/view.php?id = 4423 + +# Contraction data taken from CLDR 22.1. Only the default variant is considered. +# The languages included below are, by no means, all the langauges that have +# contractions - just a sample of langauges that have been supported + +# At the time of writing (Feb 2013), the following langauges have greater that +# 50% coverage of translation of Gramps: bg Bulgarian, ca Catalan, cs Czech, da +# Danish, de German, el Greek, en_GB, es Spanish, fi Finish, fr French, he +# Hebrew, hr Croation, hu Hungarian, it Italian, ja Japanese, lt Lithuanian, nb +# Noregian BokmÃ¥l, nn Norwegian Nynorsk, nl Dutch, pl Polish, pt_BR Portuguese +# (Brazil), pt_P Portugeuse (Portugal), ru Russian, sk Slovak, sl Slovenian, sv +# Swedish, vi Vietnamese, zh_CN Chinese. + +# Key is the language (or language and country), Value is a list of +# contractions. Each contraction consists of a tuple. First element of the +# tuple is the list of characters, second element is the string to use as the +# index entry. + +# The DUCET contractions (e.g. LATIN CAPIAL LETTER L, MIDDLE DOT) are ignored, +# as are the supresscontractions in some locales. + +CONTRACTIONS_DICT = { + # bg Bulgarian validSubLocales="bg_BG" no contractions + # ca Catalan validSubLocales="ca_AD ca_ES" + "ca" : [(("l·", "L·"), "L")], + # Czech, validSubLocales="cs_CZ" Czech_Czech Republic + "cs" : [(("ch", "cH", "Ch", "CH"), "CH")], + # Danish validSubLocales="da_DK" Danish_Denmark + "da" : [(("aa", "Aa", "AA"), "Ã…")], + # de German validSubLocales="de_AT de_BE de_CH de_DE de_LI de_LU" no + # contractions in standard collation. + # el Greek validSubLocales="el_CY el_GR" no contractions. + # es Spanish validSubLocales="es_419 es_AR es_BO es_CL es_CO es_CR es_CU + # es_DO es_EA es_EC es_ES es_GQ es_GT es_HN es_IC es_MX es_NI es_PA es_PE + # es_PH es_PR es_PY es_SV es_US es_UY es_VE" no contractions in standard + # collation. + # fi Finish validSubLocales="fi_FI" no contractions in default (phonebook) + # collation. + # fr French no collation data. + # he Hebrew validSubLocales="he_IL" no contractions + # hr Croation validSubLocales="hr_BA hr_HR" + "hr" : [(("dž", "Dž"), "dž"), + (("lj", "Lj", 'LJ'), "LJ"), + (("Nj", "NJ", "nj"), "ÇŠ")], + # Hungarian hu_HU for two and three character contractions. + "hu" : [(("cs", "Cs", "CS"), "CS"), + (("dzs", "Dzs", "DZS"), "DZS"), # order is important + (("dz", "Dz", "DZ"), "DZ"), + (("gy", "Gy", "GY"), "GY"), + (("ly", "Ly", "LY"), "LY"), + (("ny", "Ny", "NY"), "NY"), + (("sz", "Sz", "SZ"), "SZ"), + (("ty", "Ty", "TY"), "TY"), + (("zs", "Zs", "ZS"), "ZS") + ], + # it Italian no collation data. + # ja Japanese unable to process the data as it is too complex. + # lt Lithuanian no contractions. + # Norwegian BokmÃ¥l + "nb" : [(("aa", "Aa", "AA"), "Ã…")], + # nn Norwegian Nynorsk validSubLocales="nn_NO" + "nn" : [(("aa", "Aa", "AA"), "Ã…")], + # nl Dutch no collation data. + # pl Polish validSubLocales="pl_PL" no contractions + # pt Portuguese no collation data. + # ru Russian validSubLocales="ru_BY ru_KG ru_KZ ru_MD ru_RU ru_UA" no + # contractions + # Slovak, validSubLocales="sk_SK" Slovak_Slovakia + # having DZ in Slovak as a contraction was rejected in + # http://unicode.org/cldr/trac/ticket/2968 + "sk" : [(("ch", "cH", "Ch", "CH"), "Ch")], + # sl Slovenian validSubLocales="sl_SI" no contractions + # sv Swedish validSubLocales="sv_AX sv_FI sv_SE" default collation is + # "reformed" no contractions. + # vi Vietnamese validSubLocales="vi_VN" no contractions. + # zh Chinese validSubLocales="zh_Hans zh_Hans_CN zh_Hans_SG" no contractions + # in Latin characters the others are too complex. + } + + # The comment below from the glibc locale sv_SE in + # localedata/locales/sv_SE : + # + # % The letter w is normally not present in the Swedish alphabet. It + # % exists in some names in Swedish and foreign words, but is accounted + # % for as a variant of 'v'. Words and names with 'w' are in Swedish + # % ordered alphabetically among the words and names with 'v'. If two + # % words or names are only to be distinguished by 'v' or % 'w', 'v' is + # % placed before 'w'. + # + # See : http://www.gramps-project.org/bugs/view.php?id = 2933 + # + +# HOWEVER: the characters V and W in Swedish are not considered as a special +# case for several reasons. (1) The default collation for Swedish (called the +# 'reformed' collation type) regards the difference between 'v' and 'w' as a +# primary difference. (2) 'v' and 'w' in the 'standard' (non-default) collation +# type are not a contraction, just a case where the difference is secondary +# rather than primary. (3) There are plenty of other languages where a +# difference that is primary in other languages is secondary, and those are not +# specially handled. + +def first_letter(string, rlocale=glocale): + """ + Receives a string and returns the first letter + """ + if string is None or len(string) < 1: + return ' ' + + norm_unicode = normalize('NFKC', str(string)) + contractions = CONTRACTIONS_DICT.get(COLLATE_LANG) + if contractions is None: + contractions = CONTRACTIONS_DICT.get(COLLATE_LANG.split("_")[0]) + + if contractions is not None: + for contraction in contractions: + count = len(contraction[0][0]) + if (len(norm_unicode) >= count and + norm_unicode[:count] in contraction[0]): + return contraction[1] + + # no special case + return norm_unicode[0].upper() + +try: + import PyICU # pylint : disable=wrong-import-position + PRIM_COLL = PyICU.Collator.createInstance(PyICU.Locale(COLLATE_LANG)) + PRIM_COLL.setStrength(PRIM_COLL.PRIMARY) + + def primary_difference(prev_key, new_key, rlocale=glocale): + """ + Try to use the PyICU collation. + """ + + return PRIM_COLL.compare(prev_key, new_key) != 0 + +except: + def primary_difference(prev_key, new_key, rlocale=glocale): + """ + The PyICU collation is not available. + + Returns true if there is a primary difference between the two parameters + See http://www.gramps-project.org/bugs/view.php?id=2933#c9317 if + letter[i]+'a' < letter[i+1]+'b' and letter[i+1]+'a' < letter[i]+'b' is + true then the letters should be grouped together + + The test characters here must not be any that are used in contractions. + """ + + return rlocale.sort_key(prev_key + "e") >= \ + rlocale.sort_key(new_key + "f") or \ + rlocale.sort_key(new_key + "e") >= \ + rlocale.sort_key(prev_key + "f") + +def get_first_letters(dbase, handle_list, key, rlocale=glocale): + """ + get the first letters of the handle_list + + @param: handle_list -- One of a handle list for either person or + place handles or an evt types list + @param: key -- Either a person, place, or event type + + The first letter (or letters if there is a contraction) are extracted from + all the objects in the handle list. There may be duplicates, and there may + be letters where there is only a secondary or tertiary difference, not a + primary difference. The list is sorted in collation order. For each group + with secondary or tertiary differences, the first in collation sequence is + retained. For example, assume the default collation sequence (DUCET) and + names Ã…nström and Apple. These will sort in the order shown. Ã… and A have a + secondary difference. If the first letter from these names was chosen then + the inex entry would be Ã…. This is not desirable. Instead, the initial + letters are extracted (Ã… and A). These are sorted, which gives A and Ã…. Then + the first of these is used for the index entry. + """ + index_list = [] + + for handle in handle_list: + if key == _KEYPERSON: + keyname = __get_person_keyname(dbase, handle) + + elif key == _KEYPLACE: + keyname = __get_place_keyname(dbase, handle) + + else: + if rlocale != glocale: + keyname = rlocale.translation.sgettext(handle) + else: + keyname = handle + ltr = first_letter(keyname) + + index_list.append(ltr) + + # Now remove letters where there is not a primary difference + index_list.sort(key=rlocale.sort_key) + first = True + prev_index = None + for key in index_list[:]: #iterate over a slice copy of the list + if first or primary_difference(prev_index, key, rlocale): + first = False + prev_index = key + else: + index_list.remove(key) + + # return menu set letters for alphabet_navigation + return index_list + +def get_index_letter(letter, index_list, rlocale=glocale): + """ + This finds the letter in the index_list that has no primary difference from + the letter provided. See the discussion in get_first_letters above. + Continuing the example, if letter is Ã… and index_list is A, then this would + return A. + """ + for index in index_list: + if not primary_difference(letter, index, rlocale): + return index + + LOG.warning("Initial letter '%s' not found in alphabetic navigation list", + letter) + LOG.debug("filtered sorted index list %s", index_list) + return letter + +def alphabet_navigation(index_list, rlocale=glocale): + """ + Will create the alphabet navigation bar for classes IndividualListPage, + SurnameListPage, PlaceListPage, and EventList + + @param: index_list -- a dictionary of either letters or words + """ + sorted_set = defaultdict(int) + + for menu_item in index_list: + sorted_set[menu_item] += 1 + + # remove the number of each occurance of each letter + sorted_alpha_index = sorted(sorted_set, key=rlocale.sort_key) + + # if no letters, return None to its callers + if not sorted_alpha_index: + return None + + num_ltrs = len(sorted_alpha_index) + num_of_cols = 26 + num_of_rows = ((num_ltrs // num_of_cols) + 1) + + # begin alphabet navigation division + with Html("div", id="alphanav") as alphabetnavigation: + + index = 0 + for row in range(num_of_rows): + unordered = Html("ul") + + cols = 0 + while cols <= num_of_cols and index < num_ltrs: + menu_item = sorted_alpha_index[index] + if menu_item == ' ': + menu_item = ' ' + # adding title to hyperlink menu for screen readers and + # braille writers + title_txt = "Alphabet Menu: %s" % menu_item + title_str = rlocale.translation.sgettext(title_txt) + hyper = Html("a", menu_item, title=title_str, + href="#%s" % menu_item) + unordered.extend(Html("li", hyper, inline=True)) + + index += 1 + cols += 1 + num_of_rows -= 1 + + alphabetnavigation += unordered + + return alphabetnavigation + +def _has_webpage_extension(url): + """ + determine if a filename has an extension or not... + + @param: url -- filename to be checked + """ + return any(url.endswith(ext) for ext in _WEB_EXT) + +def add_birthdate(dbase, ppl_handle_list, rlocale): + """ + This will sort a list of child handles in birth order + For each entry in the list, we'll have : + birth date + The transtated birth date for the configured locale + The transtated death date for the configured locale + The handle for the child + + @param: dbase -- The database to use + @param: ppl_handle_list -- the handle for the people + @param: rlocale -- the locale for date translation + """ + sortable_individuals = [] + for person_handle in ppl_handle_list: + birth_date = 0 # dummy value in case none is found + person = dbase.get_person_from_handle(person_handle) + if person: + birth_ref = person.get_birth_ref() + birth1 = "" + if birth_ref: + birth = dbase.get_event_from_handle(birth_ref.ref) + if birth: + birth1 = rlocale.get_date(birth.get_date_object()) + birth_date = birth.get_date_object().get_sort_value() + death_event = get_death_or_fallback(dbase, person) + if death_event: + death = rlocale.get_date(death_event.get_date_object()) + else: + death = "" + sortable_individuals.append((birth_date, birth1, death, person_handle)) + + # return a list of handles with the individual's birthdate attached + return sortable_individuals + +def _find_birth_date(dbase, individual): + """ + will look for a birth date within the person's events + + @param: dbase -- The database to use + @param: individual -- The individual for who we want to find the birth date + """ + date_out = None + birth_ref = individual.get_birth_ref() + if birth_ref: + birth = dbase.get_event_from_handle(birth_ref.ref) + if birth: + date_out = birth.get_date_object() + date_out.fallback = False + else: + person_evt_ref_list = individual.get_primary_event_ref_list() + if person_evt_ref_list: + for evt_ref in person_evt_ref_list: + event = dbase.get_event_from_handle(evt_ref.ref) + if event: + if event.get_type().is_birth_fallback(): + date_out = event.get_date_object() + date_out.fallback = True + LOG.debug("setting fallback to true for '%s'", event) + break + return date_out + +def _find_death_date(dbase, individual): + """ + will look for a death date within a person's events + + @param: dbase -- The database to use + @param: individual -- The individual for who we want to find the death date + """ + date_out = None + death_ref = individual.get_death_ref() + if death_ref: + death = dbase.get_event_from_handle(death_ref.ref) + if death: + date_out = death.get_date_object() + date_out.fallback = False + else: + person_evt_ref_list = individual.get_primary_event_ref_list() + if person_evt_ref_list: + for evt_ref in person_evt_ref_list: + event = dbase.get_event_from_handle(evt_ref.ref) + if event: + if event.get_type().is_death_fallback(): + date_out = event.get_date_object() + date_out.fallback = True + LOG.debug("setting fallback to true for '%s'", event) + break + return date_out + +def build_event_data_by_individuals(dbase, ppl_handle_list): + """ + creates a list of event handles and event types for this database + + @param: dbase -- The database to use + @param: ppl_handle_list -- the handle for the people + """ + event_handle_list = [] + event_types = [] + + for person_handle in ppl_handle_list: + person = dbase.get_person_from_handle(person_handle) + if person: + + evt_ref_list = person.get_event_ref_list() + if evt_ref_list: + for evt_ref in evt_ref_list: + event = dbase.get_event_from_handle(evt_ref.ref) + if event: + + event_types.append(str(event.get_type())) + event_handle_list.append(evt_ref.ref) + + person_family_handle_list = person.get_family_handle_list() + if person_family_handle_list: + for family_handle in person_family_handle_list: + family = dbase.get_family_from_handle(family_handle) + if family: + + family_evt_ref_list = family.get_event_ref_list() + if family_evt_ref_list: + for evt_ref in family_evt_ref_list: + event = dbase.get_event_from_handle(evt_ref.ref) + if event: + event_types.append(str(event.type)) + event_handle_list.append(evt_ref.ref) + + # return event_handle_list and event types to its caller + return event_handle_list, event_types + +def name_to_md5(text): + """This creates an MD5 hex string to be used as filename.""" + + return md5(text.encode('utf-8')).hexdigest() + +def get_gendex_data(database, event_ref): + """ + Given an event, return the date and place a strings + + @param: database -- The database + @param: event_ref -- The event reference + """ + doe = "" # date of event + poe = "" # place of event + if event_ref and event_ref.ref: + event = database.get_event_from_handle(event_ref.ref) + if event: + date = event.get_date_object() + doe = format_date(date) + if event.get_place_handle(): + place_handle = event.get_place_handle() + if place_handle: + place = database.get_place_from_handle(place_handle) + if place: + poe = _pd.display(database, place, date) + return doe, poe + +def format_date(date): + """ + Format the date + """ + start = date.get_start_date() + if start != Date.EMPTY: + cal = date.get_calendar() + mod = date.get_modifier() + quality = date.get_quality() + if quality in DATE_QUALITY: + qual_text = DATE_QUALITY[quality] + " " + else: + qual_text = "" + if mod == Date.MOD_SPAN: + val = "%sFROM %s TO %s" % ( + qual_text, + make_gedcom_date(start, cal, mod, None), + make_gedcom_date(date.get_stop_date(), cal, mod, None)) + elif mod == Date.MOD_RANGE: + val = "%sBET %s AND %s" % ( + qual_text, + make_gedcom_date(start, cal, mod, None), + make_gedcom_date(date.get_stop_date(), cal, mod, None)) + else: + val = make_gedcom_date(start, cal, mod, quality) + return val + return "" + +# 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 = escape(text) + + # Deal with double quotes. + match = _HTML_DBL_QUOTES.match(text) + while match: + text = "%s" "“" "%s" "”" "%s" % match.groups() + match = _HTML_DBL_QUOTES.match(text) + # Replace remaining double quotes. + text = text.replace('"', '"') + + # Deal with single quotes. + text = text.replace("'s ", '’s ') + match = _HTML_SNG_QUOTES.match(text) + while match: + text = "%s" "‘" "%s" "’" "%s" % match.groups() + match = _HTML_SNG_QUOTES.match(text) + # Replace remaining single quotes. + text = text.replace("'", ''') + + return text + diff --git a/gramps/plugins/webreport/contact.py b/gramps/plugins/webreport/contact.py new file mode 100644 index 000000000..6a2fc4643 --- /dev/null +++ b/gramps/plugins/webreport/contact.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + ContactPage +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.utils.config import get_researcher +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import FULLCLEAR + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +class ContactPage(BasePage): + """ + This class is responsible for displaying information about the 'Researcher' + """ + def __init__(self, report, title): + """ + @param: report -- The instance of the main report class for this report + @param: title -- Is the title of the web page + """ + BasePage.__init__(self, report, title) + + output_file, sio = self.report.create_file("contact") + contactpage, head, body = self.write_header(self._('Contact')) + + # begin contact division + with Html("div", class_="content", id="Contact") as section: + body += section + + # begin summaryarea division + with Html("div", id='summaryarea') as summaryarea: + section += summaryarea + + contactimg = self.add_image('contactimg', 200) + if contactimg is not None: + summaryarea += contactimg + + # get researcher information + res = get_researcher() + + with Html("div", id='researcher') as researcher: + summaryarea += researcher + + if res.name: + res.name = res.name.replace(',,,', '') + researcher += Html("h3", res.name, inline=True) + if res.addr: + researcher += Html("span", res.addr, + id='streetaddress', inline=True) + if res.locality: + researcher += Html("span", res.locality, + id="locality", inline=True) + text = "".join([res.city, res.state, res.postal]) + if text: + city = Html("span", res.city, id='city', inline=True) + state = Html("span", res.state, id='state', inline=True) + postal = Html("span", res.postal, id='postalcode', + inline=True) + researcher += (city, state, postal) + if res.country: + researcher += Html("span", res.country, + id='country', inline=True) + if res.email: + researcher += Html("span", id='email') + ( + Html("a", res.email, + href='mailto:%s' % res.email, inline=True) + ) + + # add clear line for proper styling + summaryarea += FULLCLEAR + + note_id = report.options['contactnote'] + if note_id: + note = self.r_db.get_note_from_gramps_id(note_id) + note_text = self.get_note_format(note, False) + + # attach note + summaryarea += note_text + + # add clearline for proper styling + # add footer section + footer = self.write_footer(None) + body += (FULLCLEAR, footer) + + # send page out for porcessing + # and close the file + self.xhtml_writer(contactpage, output_file, sio, 0) diff --git a/gramps/plugins/webreport/download.py b/gramps/plugins/webreport/download.py new file mode 100644 index 000000000..176c16476 --- /dev/null +++ b/gramps/plugins/webreport/download.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + DownloadPage +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +import os +import datetime +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import (FULLCLEAR, html_escape) + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +class DownloadPage(BasePage): + """ + This class is responsible for displaying information about the Download page + """ + def __init__(self, report, title): + """ + @param: report -- The instance of the main report class for this report + @param: title -- Is the title of the web page + """ + BasePage.__init__(self, report, title) + + # do NOT include a Download Page + if not self.report.inc_download: + return + + # menu options for class + # download and description #1 + + dlfname1 = self.report.dl_fname1 + dldescr1 = self.report.dl_descr1 + + # download and description #2 + dlfname2 = self.report.dl_fname2 + dldescr2 = self.report.dl_descr2 + + # if no filenames at all, return??? + if dlfname1 or dlfname2: + + output_file, sio = self.report.create_file("download") + downloadpage, head, body = self.write_header(self._('Download')) + + # begin download page and table + with Html("div", class_="content", id="Download") as download: + body += download + + msg = self._("This page is for the user/ creator " + "of this Family Tree/ Narrative website " + "to share a couple of files with you " + "regarding their family. If there are " + "any files listed " + "below, clicking on them will allow you " + "to download them. The " + "download page and files have the same " + "copyright as the remainder " + "of these web pages.") + download += Html("p", msg, id="description") + + # begin download table and table head + with Html("table", class_="infolist download") as table: + download += table + + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + trow.extend( + Html("th", label, class_="Column" + colclass, + inline=True) + for (label, colclass) in [ + (self._("File Name"), "Filename"), + (self._("Description"), "Description"), + (self._("Last Modified"), "Modified")]) + # table body + tbody = Html("tbody") + table += tbody + + # if dlfname1 is not None, show it??? + if dlfname1: + + trow = Html("tr", id='Row01') + tbody += trow + + fname = os.path.basename(dlfname1) + # TODO dlfname1 is filename, convert disk path to URL + tcell = Html("td", class_="ColumnFilename") + ( + Html("a", fname, href=dlfname1, + title=html_escape(dldescr1)) + ) + trow += tcell + + dldescr1 = dldescr1 or " " + trow += Html("td", dldescr1, + class_="ColumnDescription", inline=True) + + tcell = Html("td", class_="ColumnModified", inline=True) + trow += tcell + if os.path.exists(dlfname1): + modified = os.stat(dlfname1).st_mtime + last_mod = datetime.datetime.fromtimestamp(modified) + tcell += last_mod + else: + tcell += " " + + # if download filename #2, show it??? + if dlfname2: + + # begin row #2 + trow = Html("tr", id='Row02') + tbody += trow + + fname = os.path.basename(dlfname2) + tcell = Html("td", class_="ColumnFilename") + ( + Html("a", fname, href=dlfname2, + title=html_escape(dldescr2)) + ) + trow += tcell + + dldescr2 = dldescr2 or " " + trow += Html("td", dldescr2, + class_="ColumnDescription", inline=True) + + tcell = Html("td", id='Col04', + class_="ColumnModified", inline=True) + trow += tcell + if os.path.exists(dlfname2): + modified = os.stat(dlfname2).st_mtime + last_mod = datetime.datetime.fromtimestamp(modified) + tcell += last_mod + else: + tcell += " " + + # clear line for proper styling + # create footer section + footer = self.write_footer(None) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(downloadpage, output_file, sio, 0) diff --git a/gramps/plugins/webreport/event.py b/gramps/plugins/webreport/event.py new file mode 100644 index 000000000..6cdc36501 --- /dev/null +++ b/gramps/plugins/webreport/event.py @@ -0,0 +1,448 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + EventPage - Event index page and individual Event pages +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from collections import defaultdict +from operator import itemgetter +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.lib import (Date, Event) +from gramps.gen.plug.report import Bibliography +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import (get_first_letters, _ALPHAEVENT, + _EVENTMAP, alphabet_navigation, + FULLCLEAR, sort_event_types, + primary_difference, + get_index_letter) + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +################################################# +# +# creates the Event List Page and EventPages +# +################################################# +class EventPages(BasePage): + """ + This class is responsible for displaying information about the 'Person' + database objects. It displays this information under the 'Events' + tab. It is told by the 'add_instances' call which 'Person's to display, + and remembers the list of persons. A single call to 'display_pages' + displays both the Event List (Index) page and all the Event + pages. + + The base class 'BasePage' is initialised once for each page that is + displayed. + """ + def __init__(self, report): + """ + @param: report -- The instance of the main report class for + this report + """ + BasePage.__init__(self, report, title="") + self.event_handle_list = [] + self.event_types = [] + self.event_dict = defaultdict(set) + + def display_pages(self, title): + """ + Generate and output the pages under the Event tab, namely the event + index and the individual event pages. + + @param: title -- Is the title of the web page + """ + LOG.debug("obj_dict[Event]") + for item in self.report.obj_dict[Event].items(): + LOG.debug(" %s", str(item)) + event_handle_list = self.report.obj_dict[Event].keys() + event_types = [] + for event_handle in event_handle_list: + event = self.r_db.get_event_from_handle(event_handle) + event_types.append(self._(event.get_type().xml_str())) + with self.r_user.progress(_("Narrated Web Site Report"), + _("Creating event pages"), + len(event_handle_list) + 1 + ) as step: + self.eventlistpage(self.report, title, event_types, + event_handle_list) + + for event_handle in event_handle_list: + step() + self.eventpage(self.report, title, event_handle) + + + def eventlistpage(self, report, title, event_types, event_handle_list): + """ + Will create the event list page + + @param: report -- The instance of the main report class for + this report + @param: title -- Is the title of the web page + @param: event_types -- A list of the type in the events database + @param: event_handle_list -- A list of event handles + """ + BasePage.__init__(self, report, title) + ldatec = 0 + prev_letter = " " + + output_file, sio = self.report.create_file("events") + eventslistpage, head, body = self.write_header(self._("Events")) + + # begin events list division + with Html("div", class_="content", id="EventList") as eventlist: + body += eventlist + + msg = self._("This page contains an index of all the events in the " + "database, sorted by their type and date (if one is " + "present). Clicking on an event’s Gramps ID " + "will open a page for that event.") + eventlist += Html("p", msg, id="description") + + # get alphabet navigation... + index_list = get_first_letters(self.r_db, event_types, + _ALPHAEVENT) + alpha_nav = alphabet_navigation(index_list, self.rlocale) + if alpha_nav: + eventlist += alpha_nav + + # begin alphabet event table + with Html("table", + class_="infolist primobjlist alphaevent") as table: + eventlist += table + + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + trow.extend( + Html("th", label, class_=colclass, inline=True) + for (label, colclass) in [(self._("Letter"), + "ColumnRowLabel"), + (self._("Type"), "ColumnType"), + (self._("Date"), "ColumnDate"), + (self._("Gramps ID"), + "ColumnGRAMPSID"), + (self._("Person"), "ColumnPerson") + ] + ) + + tbody = Html("tbody") + table += tbody + + # separate events by their type and then thier event handles + for (evt_type, + data_list) in sort_event_types(self.r_db, + event_types, + event_handle_list, + self.rlocale): + first = True + _event_displayed = [] + + # sort datalist by date of event and by event handle... + data_list = sorted(data_list, key=itemgetter(0, 1)) + first_event = True + + for (sort_value, event_handle) in data_list: + event = self.r_db.get_event_from_handle(event_handle) + _type = event.get_type() + gid = event.get_gramps_id() + if event.get_change_time() > ldatec: + ldatec = event.get_change_time() + + # check to see if we have listed this gramps_id yet? + if gid not in _event_displayed: + + # family event + if int(_type) in _EVENTMAP: + handle_list = set( + self.r_db.find_backlink_handles( + event_handle, + include_classes=['Family', 'Person'])) + else: + handle_list = set( + self.r_db.find_backlink_handles( + event_handle, + include_classes=['Person'])) + if handle_list: + + trow = Html("tr") + tbody += trow + + # set up hyperlinked letter for + # alphabet_navigation + tcell = Html("td", class_="ColumnLetter", + inline=True) + trow += tcell + + if evt_type and not evt_type.isspace(): + letter = get_index_letter( + self._(str(evt_type)[0].capitalize()), + index_list, self.rlocale) + else: + letter = " " + + if first or primary_difference(letter, + prev_letter, + self.rlocale): + first = False + prev_letter = letter + t_a = 'class = "BeginLetter BeginType"' + trow.attr = t_a + ttle = self._("Event types beginning " + "with letter %s") % letter + tcell += Html("a", letter, name=letter, + id_=letter, title=ttle, + inline=True) + else: + tcell += " " + + # display Event type if first in the list + tcell = Html("td", class_="ColumnType", + title=self._(evt_type), + inline=True) + trow += tcell + if first_event: + tcell += self._(evt_type) + if trow.attr == "": + trow.attr = 'class = "BeginType"' + else: + tcell += " " + + # event date + tcell = Html("td", class_="ColumnDate", + inline=True) + trow += tcell + date = Date.EMPTY + if event: + date = event.get_date_object() + if date and date is not Date.EMPTY: + tcell += self.rlocale.get_date(date) + else: + tcell += " " + + # Gramps ID + trow += Html("td", class_="ColumnGRAMPSID") + ( + self.event_grampsid_link(event_handle, + gid, None) + ) + + # Person(s) column + tcell = Html("td", class_="ColumnPerson") + trow += tcell + + # classname can either be a person or a family + first_person = True + + # get person(s) for ColumnPerson + sorted_list = sorted(handle_list) + self.complete_people(tcell, first_person, + sorted_list, + uplink=False) + + _event_displayed.append(gid) + first_event = False + + # add clearline for proper styling + # add footer section + footer = self.write_footer(ldatec) + body += (FULLCLEAR, footer) + + # send page ut for processing + # and close the file + self.xhtml_writer(eventslistpage, output_file, sio, ldatec) + + def _geteventdate(self, event_handle): + """ + Get the event date + + @param: event_handle -- The handle for the event to use + """ + event_date = Date.EMPTY + event = self.r_db.get_event_from_handle(event_handle) + if event: + date = event.get_date_object() + if date: + + # returns the date in YYYY-MM-DD format + return Date(date.get_year_calendar("Gregorian"), + date.get_month(), date.get_day()) + + # return empty date string + return event_date + + def event_grampsid_link(self, handle, grampsid, uplink): + """ + Create a hyperlink from event handle, but show grampsid + + @param: handle -- The handle for the event + @param: grampsid -- The gramps ID to display + @param: uplink -- If True, then "../../../" is inserted in front of + the result. + """ + url = self.report.build_url_fname_html(handle, "evt", uplink) + + # return hyperlink to its caller + return Html("a", grampsid, href=url, title=grampsid, inline=True) + + def eventpage(self, report, title, event_handle): + """ + Creates the individual event page + + @param: report -- The instance of the main report class for + this report + @param: title -- Is the title of the web page + @param: event_handle -- The event handle for the database + """ + event = report.database.get_event_from_handle(event_handle) + BasePage.__init__(self, report, title, event.get_gramps_id()) + if not event: + return None + + ldatec = event.get_change_time() + event_media_list = event.get_media_list() + + self.uplink = True + subdirs = True + evt_type = self._(event.get_type().xml_str()) + self.page_title = "%(eventtype)s" % {'eventtype' : evt_type} + self.bibli = Bibliography() + + output_file, sio = self.report.create_file(event_handle, "evt") + eventpage, head, body = self.write_header(self._("Events")) + + # start event detail division + with Html("div", class_="content", id="EventDetail") as eventdetail: + body += eventdetail + + thumbnail = self.disp_first_img_as_thumbnail(event_media_list, + event) + if thumbnail is not None: + eventdetail += thumbnail + + # display page title + eventdetail += Html("h3", self.page_title, inline=True) + + # begin eventdetail table + with Html("table", class_="infolist eventlist") as table: + eventdetail += table + + tbody = Html("tbody") + table += tbody + + evt_gid = event.get_gramps_id() + if not self.noid and evt_gid: + trow = Html("tr") + ( + Html("td", self._("Gramps ID"), + class_="ColumnAttribute", inline=True), + Html("td", evt_gid, + class_="ColumnGRAMPSID", inline=True) + ) + tbody += trow + + # get event data + # + # for more information: see get_event_data() + # + event_data = self.get_event_data(event, event_handle, + subdirs, evt_gid) + + for (label, colclass, data) in event_data: + if data: + trow = Html("tr") + ( + Html("td", label, class_="ColumnAttribute", + inline=True), + Html('td', data, class_="Column" + colclass) + ) + tbody += trow + + # Narrative subsection + notelist = event.get_note_list() + notelist = self.display_note_list(notelist) + if notelist is not None: + eventdetail += notelist + + # get attribute list + attrlist = event.get_attribute_list() + if attrlist: + attrsection, attrtable = self.display_attribute_header() + self.display_attr_list(attrlist, attrtable) + eventdetail += attrsection + + # event source references + srcrefs = self.display_ind_sources(event) + if srcrefs is not None: + eventdetail += srcrefs + + # display additional images as gallery + if self.create_media: + addgallery = self.disp_add_img_as_gallery(event_media_list, + event) + if addgallery: + eventdetail += addgallery + + # References list + ref_list = self.display_bkref_list(Event, event_handle) + if ref_list is not None: + eventdetail += ref_list + + # add clearline for proper styling + # add footer section + footer = self.write_footer(ldatec) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the page + self.xhtml_writer(eventpage, output_file, sio, ldatec) diff --git a/gramps/plugins/webreport/family.py b/gramps/plugins/webreport/family.py new file mode 100644 index 000000000..4f61ff134 --- /dev/null +++ b/gramps/plugins/webreport/family.py @@ -0,0 +1,399 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + FamilyPage - Family index page and individual Family pages +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from collections import defaultdict +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.lib import (EventType, Family) +from gramps.gen.plug.report import Bibliography +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import (get_first_letters, _KEYPERSON, + alphabet_navigation, sort_people, + primary_difference, first_letter, + FULLCLEAR, get_index_letter) + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +################################################# +# +# creates the Family List Page and Family Pages +# +################################################# +class FamilyPages(BasePage): + """ + This class is responsible for displaying information about the 'Family' + database objects. It displays this information under the 'Families' + tab. It is told by the 'add_instances' call which 'Family's to display, + and remembers the list of Family. A single call to 'display_pages' + displays both the Family List (Index) page and all the Family + pages. + + The base class 'BasePage' is initialised once for each page that is + displayed. + """ + def __init__(self, report): + """ + @param: report -- The instance of the main report class for + this report + """ + BasePage.__init__(self, report, title="") + self.family_dict = defaultdict(set) + self.person = None + self.familymappages = None + + def display_pages(self, title): + """ + Generate and output the pages under the Family tab, namely the family + index and the individual family pages. + + @param: title -- Is the title of the web page + """ + LOG.debug("obj_dict[Family]") + for item in self.report.obj_dict[Family].items(): + LOG.debug(" %s", str(item)) + + with self.r_user.progress(_("Narrated Web Site Report"), + _("Creating family pages..."), + len(self.report.obj_dict[Family]) + 1 + ) as step: + self.familylistpage(self.report, title, + self.report.obj_dict[Family].keys()) + + for family_handle in self.report.obj_dict[Family]: + step() + self.familypage(self.report, title, family_handle) + + def familylistpage(self, report, title, fam_list): + """ + Create a family index + + @param: report -- The instance of the main report class for + this report + @param: title -- Is the title of the web page + @param: fam_list -- The handle for the place to add + """ + BasePage.__init__(self, report, title) + + output_file, sio = self.report.create_file("families") + familieslistpage, head, body = self.write_header(self._("Families")) + ldatec = 0 + prev_letter = " " + + # begin Family Division + with Html("div", class_="content", id="Relationships") as relationlist: + body += relationlist + + # Families list page message + msg = self._("This page contains an index of all the " + "families/ relationships in the " + "database, sorted by their family name/ surname. " + "Clicking on a person’s " + "name will take you to their " + "family/ relationship’s page.") + relationlist += Html("p", msg, id="description") + + # go through all the families, and construct a dictionary of all the + # people and the families thay are involved in. Note that the people + # in the list may be involved in OTHER families, that are not listed + # because they are not in the original family list. + pers_fam_dict = defaultdict(list) + for family_handle in fam_list: + family = self.r_db.get_family_from_handle(family_handle) + if family: + if family.get_change_time() > ldatec: + ldatec = family.get_change_time() + husband_handle = family.get_father_handle() + spouse_handle = family.get_mother_handle() + if husband_handle: + pers_fam_dict[husband_handle].append(family) + if spouse_handle: + pers_fam_dict[spouse_handle].append(family) + + # add alphabet navigation + index_list = get_first_letters(self.r_db, pers_fam_dict.keys(), + _KEYPERSON, rlocale=self.rlocale) + alpha_nav = alphabet_navigation(index_list, self.rlocale) + if alpha_nav: + relationlist += alpha_nav + + # begin families table and table head + with Html("table", class_="infolist relationships") as table: + relationlist += table + + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + # set up page columns + trow.extend( + Html("th", trans, class_=colclass, inline=True) + for trans, colclass in [(self._("Letter"), + "ColumnRowLabel"), + (self._("Person"), "ColumnPartner"), + (self._("Family"), "ColumnPartner"), + (self._("Marriage"), "ColumnDate"), + (self._("Divorce"), "ColumnDate")] + ) + + tbody = Html("tbody") + table += tbody + + # begin displaying index list + ppl_handle_list = sort_people(self.r_db, pers_fam_dict.keys(), + self.rlocale) + first = True + for (surname, handle_list) in ppl_handle_list: + + if surname and not surname.isspace(): + letter = get_index_letter(first_letter(surname), + index_list, + self.rlocale) + else: + letter = ' ' + + # get person from sorted database list + for person_handle in sorted( + handle_list, key=self.sort_on_name_and_grampsid): + person = self.r_db.get_person_from_handle(person_handle) + if person: + family_list = person.get_family_handle_list() + first_family = True + for family_handle in family_list: + get_family = self.r_db.get_family_from_handle + family = get_family(family_handle) + trow = Html("tr") + tbody += trow + + tcell = Html("td", class_="ColumnRowLabel") + trow += tcell + + if first or primary_difference(letter, + prev_letter, + self.rlocale): + first = False + prev_letter = letter + trow.attr = 'class="BeginLetter"' + ttle = self._("Families beginning with " + "letter ") + tcell += Html("a", letter, name=letter, + title=ttle + letter, + inline=True) + else: + tcell += ' ' + + tcell = Html("td", class_="ColumnPartner") + trow += tcell + + if first_family: + trow.attr = 'class ="BeginFamily"' + + tcell += self.new_person_link( + person_handle, uplink=self.uplink) + + first_family = False + else: + tcell += ' ' + + tcell = Html("td", class_="ColumnPartner") + trow += tcell + + tcell += self.family_link( + family.get_handle(), + self.report.get_family_name(family), + family.get_gramps_id(), self.uplink) + + # family events; such as marriage and divorce + # events + fam_evt_ref_list = family.get_event_ref_list() + tcell1 = Html("td", class_="ColumnDate", + inline=True) + tcell2 = Html("td", class_="ColumnDate", + inline=True) + trow += (tcell1, tcell2) + + if fam_evt_ref_list: + fam_evt_srt_ref_list = sorted( + fam_evt_ref_list, + key=self.sort_on_grampsid) + for evt_ref in fam_evt_srt_ref_list: + evt = self.r_db.get_event_from_handle( + evt_ref.ref) + if evt: + evt_type = evt.get_type() + if evt_type in [EventType.MARRIAGE, + EventType.DIVORCE]: + + cell = self.rlocale.get_date( + evt.get_date_object()) + if (evt_type == + EventType.MARRIAGE): + tcell1 += cell + else: + tcell1 += ' ' + + if (evt_type == + EventType.DIVORCE): + tcell2 += cell + else: + tcell2 += ' ' + else: + tcell1 += ' ' + tcell2 += ' ' + first_family = False + + # add clearline for proper styling + # add footer section + footer = self.write_footer(ldatec) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(familieslistpage, output_file, sio, ldatec) + + def familypage(self, report, title, family_handle): + """ + Create a family page + + @param: report -- The instance of the main report class for + this report + @param: title -- Is the title of the web page + @param: family_handle -- The handle for the family to add + """ + family = report.database.get_family_from_handle(family_handle) + if not family: + return + BasePage.__init__(self, report, title, family.get_gramps_id()) + ldatec = family.get_change_time() + + self.bibli = Bibliography() + self.uplink = True + family_name = self.report.get_family_name(family) + self.page_title = family_name + + self.familymappages = report.options["familymappages"] + + output_file, sio = self.report.create_file(family.get_handle(), "fam") + familydetailpage, head, body = self.write_header(family_name) + + # begin FamilyDetaill division + with Html("div", class_="content", + id="RelationshipDetail") as relationshipdetail: + body += relationshipdetail + + # family media list for initial thumbnail + if self.create_media: + media_list = family.get_media_list() + # If Event pages are not being created, then we need to display + # the family event media here + if not self.inc_events: + for evt_ref in family.get_event_ref_list(): + event = self.r_db.get_event_from_handle(evt_ref.ref) + media_list += event.get_media_list() + thumbnail = self.disp_first_img_as_thumbnail(media_list, + family) + if thumbnail: + relationshipdetail += thumbnail + + self.person = None # no longer used + + relationshipdetail += Html( + "h2", self.page_title, inline=True) + ( + Html('sup') + (Html('small') + + self.get_citation_links( + family.get_citation_list()))) + + # display relationships + families = self.display_family_relationships(family, None) + if families is not None: + relationshipdetail += families + + # display additional images as gallery + if self.create_media and media_list: + addgallery = self.disp_add_img_as_gallery(media_list, family) + if addgallery: + relationshipdetail += addgallery + + # Narrative subsection + notelist = family.get_note_list() + if notelist: + relationshipdetail += self.display_note_list(notelist) + + # display family LDS ordinance... + family_lds_ordinance_list = family.get_lds_ord_list() + if family_lds_ordinance_list: + relationshipdetail += self.display_lds_ordinance(family) + + # get attribute list + attrlist = family.get_attribute_list() + if attrlist: + attrsection, attrtable = self.display_attribute_header() + self.display_attr_list(attrlist, attrtable) + relationshipdetail += attrsection + + # source references + srcrefs = self.display_ind_sources(family) + if srcrefs: + relationshipdetail += srcrefs + + # add clearline for proper styling + # add footer section + footer = self.write_footer(ldatec) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(familydetailpage, output_file, sio, ldatec) diff --git a/gramps/plugins/webreport/home.py b/gramps/plugins/webreport/home.py new file mode 100644 index 000000000..a27673459 --- /dev/null +++ b/gramps/plugins/webreport/home.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + HomePage +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import FULLCLEAR + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +class HomePage(BasePage): + """ + This class is responsible for displaying information about the Home page. + """ + def __init__(self, report, title): + """ + @param: report -- The instance of the main report class for + this report + @param: title -- Is the title of the web page + """ + BasePage.__init__(self, report, title) + ldatec = 0 + + output_file, sio = self.report.create_file("index") + homepage, head, body = self.write_header(self._('Home')) + + # begin home division + with Html("div", class_="content", id="Home") as section: + body += section + + homeimg = self.add_image('homeimg') + if homeimg is not None: + section += homeimg + + note_id = report.options['homenote'] + if note_id: + note = self.r_db.get_note_from_gramps_id(note_id) + note_text = self.get_note_format(note, False) + + # attach note + section += note_text + + # last modification of this note + ldatec = note.get_change_time() + + # create clear line for proper styling + # create footer section + footer = self.write_footer(ldatec) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(homepage, output_file, sio, ldatec) diff --git a/gramps/plugins/webreport/introduction.py b/gramps/plugins/webreport/introduction.py new file mode 100644 index 000000000..c88fa50a1 --- /dev/null +++ b/gramps/plugins/webreport/introduction.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + IntroductionPage +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import FULLCLEAR + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +class IntroductionPage(BasePage): + """ + This class is responsible for displaying information + about the introduction page. + """ + def __init__(self, report, title): + """ + @param: report -- The instance of the main report class for + this report + @param: title -- Is the title of the web page + """ + BasePage.__init__(self, report, title) + ldatec = 0 + + output_file, sio = self.report.create_file(report.intro_fname) + intropage, head, body = self.write_header(self._('Introduction')) + + # begin Introduction division + with Html("div", class_="content", id="Introduction") as section: + body += section + + introimg = self.add_image('introimg') + if introimg is not None: + section += introimg + + note_id = report.options['intronote'] + if note_id: + note = self.r_db.get_note_from_gramps_id(note_id) + note_text = self.get_note_format(note, False) + + # attach note + section += note_text + + # last modification of this note + ldatec = note.get_change_time() + + # add clearline for proper styling + # create footer section + footer = self.write_footer(ldatec) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(intropage, output_file, sio, ldatec) diff --git a/gramps/plugins/webreport/media.py b/gramps/plugins/webreport/media.py new file mode 100644 index 000000000..0e8def04b --- /dev/null +++ b/gramps/plugins/webreport/media.py @@ -0,0 +1,641 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + MediaPage - Media index page and individual Media pages +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +import gc +import os +import shutil +import tempfile +from collections import defaultdict +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.lib import (Date, Media) +from gramps.gen.plug.report import Bibliography +from gramps.gen.utils.file import media_path_full +from gramps.gen.utils.thumbnails import run_thumbnailer +from gramps.gen.utils.image import image_size # , resize_to_jpeg_buffer +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import (FULLCLEAR, _WRONGMEDIAPATH, + html_escape) + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +################################################# +# +# creates the Media List Page and Media Pages +# +################################################# +class MediaPages(BasePage): + """ + This class is responsible for displaying information about the 'Media' + database objects. It displays this information under the 'Individuals' + tab. It is told by the 'add_instances' call which 'Media's to display, + and remembers the list of persons. A single call to 'display_pages' + displays both the Individual List (Index) page and all the Individual + pages. + + The base class 'BasePage' is initialised once for each page that is + displayed. + """ + def __init__(self, report): + """ + @param: report -- The instance of the main report class for this report + """ + BasePage.__init__(self, report, title="") + self.media_dict = defaultdict(set) + + def display_pages(self, title): + """ + Generate and output the pages under the Media tab, namely the media + index and the individual media pages. + + @param: title -- Is the title of the web page + """ + LOG.debug("obj_dict[Media]") + for item in self.report.obj_dict[Media].items(): + LOG.debug(" %s", str(item)) + with self.r_user.progress(_("Narrated Web Site Report"), + _("Creating media pages"), + len(self.report.obj_dict[Media]) + 1 + ) as step: + # bug 8950 : it seems it's better to sort on desc + gid. + def sort_by_desc_and_gid(obj): + """ + Sort by media description and gramps ID + """ + return (obj.desc.lower(), obj.gramps_id) + + sorted_media_handles = sorted( + self.report.obj_dict[Media].keys(), + key=lambda x: sort_by_desc_and_gid( + self.r_db.get_media_from_handle(x))) + self.medialistpage(self.report, title, sorted_media_handles) + + prev = None + total = len(sorted_media_handles) + index = 1 + for handle in sorted_media_handles: + gc.collect() # Reduce memory usage when there are many images. + next_ = None if index == total else sorted_media_handles[index] + step() + self.mediapage(self.report, title, + handle, (prev, next_, index, total)) + prev = handle + index += 1 + + def medialistpage(self, report, title, sorted_media_handles): + """ + Generate and output the Media index page. + + @param: report -- The instance of the main report class + for this report + @param: title -- Is the title of the web page + @param: sorted_media_handles -- A list of the handles of the media to be + displayed sorted by the media title + """ + BasePage.__init__(self, report, title) + + output_file, sio = self.report.create_file("media") + medialistpage, head, body = self.write_header(self._('Media')) + + ldatec = 0 + # begin gallery division + with Html("div", class_="content", id="Gallery") as medialist: + body += medialist + + msg = self._("This page contains an index of all the media objects " + "in the database, sorted by their title. Clicking on " + "the title will take you to that " + "media object’s page. " + "If you see media size dimensions " + "above an image, click on the " + "image to see the full sized version. ") + medialist += Html("p", msg, id="description") + + # begin gallery table and table head + with Html("table", + class_="infolist primobjlist gallerylist") as table: + medialist += table + + # begin table head + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + trow.extend( + Html("th", trans, class_=colclass, inline=True) + for trans, colclass in [(" ", "ColumnRowLabel"), + (self._("Media | Name"), + "ColumnName"), + (self._("Date"), "ColumnDate"), + (self._("Mime Type"), "ColumnMime")] + ) + + # begin table body + tbody = Html("tbody") + table += tbody + + index = 1 + for media_handle in sorted_media_handles: + media = self.r_db.get_media_from_handle(media_handle) + if media: + if media.get_change_time() > ldatec: + ldatec = media.get_change_time() + title = media.get_description() or "[untitled]" + + trow = Html("tr") + tbody += trow + + media_data_row = [ + [index, "ColumnRowLabel"], + [self.media_ref_link(media_handle, + title), "ColumnName"], + [self.rlocale.get_date(media.get_date_object()), + "ColumnDate"], + [media.get_mime_type(), "ColumnMime"]] + + trow.extend( + Html("td", data, class_=colclass) + for data, colclass in media_data_row + ) + index += 1 + + def sort_by_desc_and_gid(obj): + """ + Sort by media description and gramps ID + """ + return (obj.desc, obj.gramps_id) + + unused_media_handles = [] + if self.create_unused_media: + # add unused media + media_list = self.r_db.get_media_handles() + for media_ref in media_list: + if media_ref not in self.report.obj_dict[Media]: + unused_media_handles.append(media_ref) + unused_media_handles = sorted( + unused_media_handles, + key=lambda x: sort_by_desc_and_gid( + self.r_db.get_media_from_handle(x))) + + idx = 1 + prev = None + total = len(unused_media_handles) + if total > 0: + trow += Html("tr") + trow.extend( + Html("td", Html("h4", " "), inline=True) + + Html("td", + Html("h4", + self._("Below unused media objects"), + inline=True), + class_="") + + Html("td", Html("h4", " "), inline=True) + + Html("td", Html("h4", " "), inline=True) + ) + for media_handle in unused_media_handles: + media = self.r_db.get_media_from_handle(media_handle) + gc.collect() # Reduce memory usage when many images. + next_ = None if idx == total else unused_media_handles[idx] + trow += Html("tr") + media_data_row = [ + [index, "ColumnRowLabel"], + [self.media_ref_link(media_handle, + media.get_description()), + "ColumnName"], + [self.rlocale.get_date(media.get_date_object()), + "ColumnDate"], + [media.get_mime_type(), "ColumnMime"]] + trow.extend( + Html("td", data, class_=colclass) + for data, colclass in media_data_row + ) + self.mediapage(self.report, title, + media_handle, (prev, next_, index, total)) + prev = media_handle + index += 1 + idx += 1 + + # add footer section + # add clearline for proper styling + footer = self.write_footer(ldatec) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(medialistpage, output_file, sio, ldatec) + + def media_ref_link(self, handle, name, uplink=False): + """ + Create a reference link to a media + + @param: handle -- The media handle + @param: name -- The name to use for the link + @param: uplink -- If True, then "../../../" is inserted in front of the + result. + """ + # get media url + url = self.report.build_url_fname_html(handle, "img", uplink) + + # get name + name = html_escape(name) + + # begin hyper link + hyper = Html("a", name, href=url, title=name) + + # return hyperlink to its callers + return hyper + + def mediapage(self, report, title, media_handle, info): + """ + Generate and output an individual Media page. + + @param: report -- The instance of the main report class + for this report + @param: title -- Is the title of the web page + @param: media_handle -- The media handle to use + @param: info -- A tuple containing the media handle for the + next and previous media, the current page + number, and the total number of media pages + """ + media = report.database.get_media_from_handle(media_handle) + BasePage.__init__(self, report, title, media.gramps_id) + (prev, next_, page_number, total_pages) = info + + ldatec = media.get_change_time() + + # get media rectangles + _region_items = self.media_ref_rect_regions(media_handle) + + output_file, sio = self.report.create_file(media_handle, "img") + self.uplink = True + + self.bibli = Bibliography() + + # get media type to be used primarily with "img" tags + mime_type = media.get_mime_type() + #mtype = get_description(mime_type) + + if mime_type: + #note_only = False + newpath = self.copy_source_file(media_handle, media) + target_exists = newpath is not None + else: + #note_only = True + target_exists = False + + self.copy_thumbnail(media_handle, media) + self.page_title = media.get_description() + esc_page_title = html_escape(self.page_title) + (mediapage, head, + body) = self.write_header("%s - %s" % (self._("Media"), + self.page_title)) + + # if there are media rectangle regions, attach behaviour style sheet + if _region_items: + + fname = "/".join(["css", "behaviour.css"]) + url = self.report.build_url_fname(fname, None, self.uplink) + head += Html("link", href=url, type="text/css", + media="screen", rel="stylesheet") + + # begin MediaDetail division + with Html("div", class_="content", id="GalleryDetail") as mediadetail: + body += mediadetail + + # media navigation + with Html("div", id="GalleryNav", role="navigation") as medianav: + mediadetail += medianav + if prev: + medianav += self.media_nav_link(prev, + self._("Previous"), True) + data = self._('%(strong1_strt)s%(page_number)d%(strong_end)s ' + 'of %(strong2_strt)s%(total_pages)d%(strong_end)s' + ) % {'strong1_strt' : + '', + 'strong2_strt' : '', + 'strong_end' : '', + 'page_number' : page_number, + 'total_pages' : total_pages} + medianav += Html("span", data, id="GalleryPages") + if next_: + medianav += self.media_nav_link(next_, self._("Next"), True) + + # missing media error message + errormsg = self._("The file has been moved or deleted.") + + # begin summaryarea division + with Html("div", id="summaryarea") as summaryarea: + mediadetail += summaryarea + if mime_type: + if mime_type.startswith("image"): + if not target_exists: + with Html("div", id="MediaDisplay") as mediadisplay: + summaryarea += mediadisplay + mediadisplay += Html("span", errormsg, + class_="MissingImage") + + else: + # Check how big the image is relative to the + # requested 'initial' image size. + # If it's significantly bigger, scale it down to + # improve the site's responsiveness. We don't want + # the user to have to await a large download + # unnecessarily. Either way, set the display image + # size as requested. + orig_image_path = media_path_full(self.r_db, + media.get_path()) + #mtime = os.stat(orig_image_path).st_mtime + (width, height) = image_size(orig_image_path) + max_width = self.report.options[ + 'maxinitialimagewidth'] + max_height = self.report.options[ + 'maxinitialimageheight'] + if width != 0 and height != 0: + scale_w = (float(max_width)/width) or 1 + # the 'or 1' is so that a max of + # zero is ignored + scale_h = (float(max_height)/height) or 1 + else: + scale_w = 1.0 + scale_h = 1.0 + scale = min(scale_w, scale_h, 1.0) + new_width = int(width*scale) + new_height = int(height*scale) + + # TODO. Convert disk path to URL. + url = self.report.build_url_fname(orig_image_path, + None, self.uplink) + with Html("div", id="GalleryDisplay", + style='width: %dpx; height: %dpx' % ( + new_width, + new_height)) as mediadisplay: + summaryarea += mediadisplay + + # 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 _region_items: + ordered = Html("ol", class_="RegionBox") + mediadisplay += ordered + while len(_region_items) > 0: + (name, coord_x, coord_y, + width, height, linkurl + ) = _region_items.pop() + ordered += Html( + "li", + style="left:%d%%; " + "top:%d%%; " + "width:%d%%; " + "height:%d%%;" % ( + coord_x, coord_y, + width, height)) + ( + Html("a", name, + href=linkurl) + ) + + # display the image + if orig_image_path != newpath: + url = self.report.build_url_fname( + newpath, None, self.uplink) + mediadisplay += Html("a", href=url) + ( + Html("img", width=new_width, + height=new_height, src=url, + alt=esc_page_title) + ) + else: + dirname = tempfile.mkdtemp() + thmb_path = os.path.join(dirname, "document.png") + if run_thumbnailer(mime_type, + media_path_full(self.r_db, + media.get_path()), + thmb_path, 320): + try: + path = self.report.build_path( + "preview", media.get_handle()) + npath = os.path.join(path, media.get_handle()) + npath += ".png" + self.report.copy_file(thmb_path, npath) + path = npath + os.unlink(thmb_path) + except EnvironmentError: + path = os.path.join("images", "document.png") + else: + path = os.path.join("images", "document.png") + os.rmdir(dirname) + + with Html("div", id="GalleryDisplay") as mediadisplay: + summaryarea += mediadisplay + + img_url = self.report.build_url_fname(path, + None, + self.uplink) + if target_exists: + # TODO. Convert disk path to URL + url = self.report.build_url_fname(newpath, + None, + self.uplink) + hyper = Html("a", href=url, + title=esc_page_title) + ( + Html("img", src=img_url, + alt=esc_page_title) + ) + mediadisplay += hyper + else: + mediadisplay += Html("span", errormsg, + class_="MissingImage") + else: + with Html("div", id="GalleryDisplay") as mediadisplay: + summaryarea += mediadisplay + url = self.report.build_url_image("document.png", + "images", self.uplink) + mediadisplay += Html("img", src=url, + alt=esc_page_title, + title=esc_page_title) + + # media title + title = Html("h3", html_escape(self.page_title.strip()), + inline=True) + summaryarea += title + + # begin media table + with Html("table", class_="infolist gallery") as table: + summaryarea += table + + # Gramps ID + media_gid = media.gramps_id + if not self.noid and media_gid: + trow = Html("tr") + ( + Html("td", self._("Gramps ID"), + class_="ColumnAttribute", + inline=True), + Html("td", media_gid, class_="ColumnValue", + inline=True) + ) + table += trow + + # mime type + if mime_type: + trow = Html("tr") + ( + Html("td", self._("File Type"), + class_="ColumnAttribute", + inline=True), + Html("td", mime_type, class_="ColumnValue", + inline=True) + ) + table += trow + + # media date + date = media.get_date_object() + if date and date is not Date.EMPTY: + trow = Html("tr") + ( + Html("td", self._("Date"), class_="ColumnAttribute", + inline=True), + Html("td", self.rlocale.get_date(date), + class_="ColumnValue", + inline=True) + ) + table += trow + + # get media notes + notelist = self.display_note_list(media.get_note_list()) + if notelist is not None: + mediadetail += notelist + + # get attribute list + attrlist = media.get_attribute_list() + if attrlist: + attrsection, attrtable = self.display_attribute_header() + self.display_attr_list(attrlist, attrtable) + mediadetail += attrsection + + # get media sources + srclist = self.display_media_sources(media) + if srclist is not None: + mediadetail += srclist + + # get media references + reflist = self.display_bkref_list(Media, media_handle) + if reflist is not None: + mediadetail += reflist + + # add clearline for proper styling + # add footer section + footer = self.write_footer(ldatec) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(mediapage, output_file, sio, ldatec) + + def media_nav_link(self, handle, name, uplink=False): + """ + Creates the Media Page Navigation hyperlinks for Next and Prev + """ + url = self.report.build_url_fname_html(handle, "img", uplink) + name = html_escape(name) + return Html("a", name, name=name, id=name, href=url, + title=name, inline=True) + + def display_media_sources(self, photo): + """ + Display media sources + + @param: photo -- The source object (image, pdf, ...) + """ + list(map( + lambda i: self.bibli.add_reference( + self.r_db.get_citation_from_handle(i)), + photo.get_citation_list())) + sourcerefs = self.display_source_refs(self.bibli) + + # return source references to its caller + return sourcerefs + + def copy_source_file(self, handle, photo): + """ + Copy source file in the web tree. + + @param: handle -- Handle of the source + @param: photo -- The source object (image, pdf, ...) + """ + ext = os.path.splitext(photo.get_path())[1] + to_dir = self.report.build_path('images', handle) + newpath = os.path.join(to_dir, handle) + ext + + fullpath = media_path_full(self.r_db, photo.get_path()) + if not os.path.isfile(fullpath): + _WRONGMEDIAPATH.append([photo.get_gramps_id(), fullpath]) + return None + try: + mtime = os.stat(fullpath).st_mtime + 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) + new_file = os.path.join(self.html_dir, newpath) + shutil.copyfile(fullpath, new_file) + os.utime(new_file, (mtime, mtime)) + return newpath + except (IOError, OSError) as msg: + error = _("Missing media object:" + ) + "%s (%s)" % (photo.get_description(), + photo.get_gramps_id()) + self.r_user.warn(error, str(msg)) + return None diff --git a/gramps/plugins/webreport/narrativeweb.py b/gramps/plugins/webreport/narrativeweb.py index 5fd8ed36b..4820d089d 100644 --- a/gramps/plugins/webreport/narrativeweb.py +++ b/gramps/plugins/webreport/narrativeweb.py @@ -12,7 +12,7 @@ # Copyright (C) 2008-2011 Rob G. Healey # Copyright (C) 2010 Doug Blank # Copyright (C) 2010 Jakim Friant -# Copyright (C) 2010-2016 Serge Noiraud +# Copyright (C) 2010-2017 Serge Noiraud # Copyright (C) 2011 Tim G L Lyons # Copyright (C) 2013 Benny Malengier # Copyright (C) 2016 Allen Crider @@ -41,8299 +41,83 @@ Classes: NavWebOptions - class that defines the options and provides the handling interface - BasePage - super class for producing a web page. This class is instantiated - once for each page. Provdes various common functions. - -Classes for producing the web pages: - SurnamePage - creates list of individuals with same surname - FamilyPage - Family index page and individual Family pages - PlacePage - Place index page and individual Place pages - EventPage - Event index page and individual Event pages - SurnameListPage - Index for first letters of surname - IntroductionPage - HomePage - CitationPages - dummy - SourcePage - Source index page and individual Source pages - MediaPage - Media index page and individual Media pages - ThimbnailPreviewPage - DownloadPage - ContactPage - PersonPage - Person index page and individual `Person pages - RepositoryPage - Repository index page and individual Repository pages - AddressBookListPage - AddressBookPage """ #------------------------------------------------ # python modules #------------------------------------------------ +import logging from functools import partial -import gc import os import sys -import re -import copy -from hashlib import md5 -import time, datetime +import time import shutil import tarfile -import tempfile from io import BytesIO, TextIOWrapper -from unicodedata import normalize from collections import defaultdict -from xml.sax.saxutils import escape - -from operator import itemgetter -from decimal import Decimal, getcontext -getcontext().prec = 8 - -#------------------------------------------------ -# Set up logging -#------------------------------------------------ -import logging -LOG = logging.getLogger(".NarrativeWeb") +from decimal import getcontext #------------------------------------------------ # Gramps module #------------------------------------------------ from gramps.gen.const import GRAMPS_LOCALE as glocale -_ = glocale.translation.sgettext -from gramps.gen.lib import (ChildRefType, Date, EventType, FamilyRelType, Name, - NameType, Person, UrlType, NoteType, PlaceType, - EventRoleType, Family, Event, Place, Source, +from gramps.gen.lib import (EventType, Name, + Person, + Family, Event, Place, Source, Citation, Media, Repository, Note, Tag) -from gramps.gen.lib.date import Today -from gramps.gen.const import PROGRAM_NAME, URL_HOMEPAGE -from gramps.version import VERSION from gramps.gen.plug.menu import (PersonOption, NumberOption, StringOption, BooleanOption, EnumeratedListOption, FilterOption, NoteOption, MediaOption, DestinationOption) -from gramps.gen.plug.report import (Report, Bibliography) +from gramps.gen.plug.report import Report from gramps.gen.plug.report import utils from gramps.gen.plug.report import MenuReportOptions from gramps.gen.plug.report import stdoptions - -from gramps.gen.utils.config import get_researcher -from gramps.gen.utils.string import conf_strings -from gramps.gen.utils.file import media_path_full -from gramps.gen.utils.alive import probably_alive from gramps.gen.constfunc import win, get_curr_dir from gramps.gen.config import config -from gramps.gen.utils.thumbnails import get_thumbnail_path, run_thumbnailer -from gramps.gen.utils.image import image_size # , resize_to_jpeg_buffer from gramps.gen.display.name import displayer as _nd from gramps.gen.display.place import displayer as _pd from gramps.gen.proxy import CacheProxyDb from gramps.plugins.lib.libhtmlconst import _CHARACTER_SETS, _CC, _COPY_OPTIONS -from gramps.gen.datehandler import get_date -from gramps.gen.utils.db import get_birth_or_fallback, get_death_or_fallback - -# import HTML Class from src/plugins/lib/libhtml.py -from gramps.plugins.lib.libhtml import Html, xml_lang - -# import styled notes from src/plugins/lib/libhtmlbackend.py -from gramps.plugins.lib.libhtmlbackend import HtmlBackend, process_spaces - -from gramps.plugins.lib.libgedcom import make_gedcom_date, DATE_QUALITY -from gramps.gen.utils.place import conv_lat_lon -from gramps.gen.plug import BasePluginManager - from gramps.gen.relationship import get_relationship_calculator -from gramps.gen.utils.location import get_main_location -COLLATE_LANG = glocale.collation -SORT_KEY = glocale.sort_key #------------------------------------------------ -# Everything below this point is identical for gramps34 (post 3.4.2), -# gramps40 and trunk +# specific narrative web import #------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.person import PersonPages +from gramps.plugins.webreport.family import FamilyPages +from gramps.plugins.webreport.event import EventPages +from gramps.plugins.webreport.media import MediaPages +from gramps.plugins.webreport.place import PlacePages +from gramps.plugins.webreport.source import SourcePages +from gramps.plugins.webreport.repository import RepositoryPages +from gramps.plugins.webreport.citation import CitationPages +from gramps.plugins.webreport.surnamelist import SurnameListPage +from gramps.plugins.webreport.surname import SurnamePage +from gramps.plugins.webreport.thumbnail import ThumbnailPreviewPage +from gramps.plugins.webreport.statistics import StatisticsPage +from gramps.plugins.webreport.home import HomePage +from gramps.plugins.webreport.contact import ContactPage +from gramps.plugins.webreport.download import DownloadPage +from gramps.plugins.webreport.introduction import IntroductionPage +from gramps.plugins.webreport.addressbook import AddressBookPage +from gramps.plugins.webreport.addressbooklist import AddressBookListPage + +from gramps.plugins.webreport.common import (get_gendex_data, + HTTP, HTTPS, _WEB_EXT, CSS, + _NARRATIVESCREEN, _NARRATIVEPRINT, + _WRONGMEDIAPATH, sort_people) + +LOG = logging.getLogger(".NarrativeWeb") +_ = glocale.translation.sgettext +getcontext().prec = 8 #------------------------------------------------ # constants #------------------------------------------------ -HTTP = "http://" -HTTPS = "https://" - -GOOGLE_MAPS = 'https://maps.googleapis.com/maps/' -# javascript code for marker path -MARKER_PATH = """ - var marker_png = '%s' -""" -# javascript code for Google's FamilyLinks... -FAMILYLINKS = """ - var tracelife = %s - - function initialize() { - var myLatLng = new google.maps.LatLng(%s, %s); - - var mapOptions = { - scaleControl: true, - panControl: true, - backgroundColor: '#000000', - zoom: %d, - center: myLatLng, - mapTypeId: google.maps.MapTypeId.ROADMAP - }; - var map = new google.maps.Map(document.getElementById("map_canvas"), - mapOptions); - - var flightPath = new google.maps.Polyline({ - path: tracelife, - strokeColor: "#FF0000", - strokeOpacity: 1.0, - strokeWeight: 2 - }); - - flightPath.setMap(map); - }""" - -# javascript for Google's Drop Markers... -DROPMASTERS = """ - var markers = []; - var iterator = 0; - - var tracelife = %s - var map; - var myLatLng = new google.maps.LatLng(%s, %s); - - function initialize() { - var mapOptions = { - scaleControl: true, - zoomControl: true, - zoom: %d, - mapTypeId: google.maps.MapTypeId.ROADMAP, - center: myLatLng, - }; - map = new google.maps.Map(document.getElementById("map_canvas"), - mapOptions); - }; - - function drop() { - for (var i = 0; i < tracelife.length; i++) { - setTimeout(function() { - addMarker(); - }, i * 1000); - } - } - - function addMarker() { - var location = tracelife[iterator]; - var myLatLng = new google.maps.LatLng(location[1], location[2]); - - markers.push(new google.maps.Marker({ - position: myLatLng, - map: map, - draggable: true, - title: location[0], - animation: google.maps.Animation.DROP - })); - iterator++; - }""" - -# javascript for Google's Markers... -MARKERS = """ - var tracelife = %s - var map; - var myLatLng = new google.maps.LatLng(%s, %s); - - function initialize() { - var mapOptions = { - scaleControl: true, - panControl: true, - backgroundColor: '#000000', - zoom: %d, - center: myLatLng, - mapTypeId: google.maps.MapTypeId.ROADMAP - }; - map = new google.maps.Map(document.getElementById("map_canvas"), - mapOptions); - addMarkers(); - } - - function addMarkers() { - var bounds = new google.maps.LatLngBounds(); - - for (var i = 0; i < tracelife.length; i++) { - var location = tracelife[i]; - var myLatLng = new google.maps.LatLng(location[1], location[2]); - - var marker = new google.maps.Marker({ - position: myLatLng, - draggable: true, - title: location[0], - map: map, - zIndex: location[3] - }); - bounds.extend(myLatLng); - if ( i > 1 ) { map.fitBounds(bounds); }; - } - }""" - -# javascript for OpenStreetMap's markers... -OSM_MARKERS = """ - function initialize(){ - var map; - var tracelife = %s; - var iconStyle = new ol.style.Style({ - image: new ol.style.Icon(({ - opacity: 1.0, - src: marker_png - })) - }); - var markerSource = new ol.source.Vector({ - }); - for (var i = 0; i < tracelife.length; i++) { - var loc = tracelife[i]; - var iconFeature = new ol.Feature({ - geometry: new ol.geom.Point(ol.proj.transform([loc[0], loc[1]], - 'EPSG:4326', 'EPSG:3857')), - name: loc[2], - }); - iconFeature.setStyle(iconStyle); - markerSource.addFeature(iconFeature); - } - markerLayer = new ol.layer.Vector({ - source: markerSource, - style: iconStyle - }); - var centerCoord = new ol.proj.transform([%s, %s], 'EPSG:4326', 'EPSG:3857'); - map= new ol.Map({ - target: 'map_canvas', - layers: [new ol.layer.Tile({ source: new ol.source.OSM() }), - markerLayer], - view: new ol.View({ center: centerCoord, zoom: %d }) - }); - var element = document.getElementById('popup'); - var tooltip = new ol.Overlay({ - element: element, - positioning: 'bottom-center', - stopEvent: false - }); - map.addOverlay(tooltip); - var displayFeatureInfo = function(pixel) { - var feature = map.forEachFeatureAtPixel(pixel, function(feature, layer) { - return feature; - }); - var info = document.getElementById('popup'); - if (feature) { - var geometry = feature.getGeometry(); - var coord = geometry.getCoordinates(); - tooltip.setPosition(coord); - $(element).siblings('.popover').css({ width: '250px' }); - $(element).siblings('.popover').css({ background: '#aaa' }); - $(info).popover({ - 'placement': 'auto', - 'html': true, - 'content': feature.get('name') - }); - $(info).popover('show'); - } else { - // TODO : some warning with firebug here - $(info).popover('destroy'); - $('.popover').remove(); - } - }; - map.on('pointermove', function(evt) { - if (evt.dragging) { - return; - } - var pixel = map.getEventPixel(evt.originalEvent); - displayFeatureInfo(pixel); - }); - map.on('click', function(evt) { - displayFeatureInfo(evt.pixel); - }); - }; -""" -# there is no need to add an ending "", -# as it will be added automatically by libhtml() - -# Events that are usually a family event -_EVENTMAP = set([EventType.MARRIAGE, EventType.MARR_ALT, - EventType.MARR_SETTL, EventType.MARR_LIC, - EventType.MARR_CONTR, EventType.MARR_BANNS, - EventType.ENGAGEMENT, EventType.DIVORCE, - EventType.DIV_FILING]) - -# define clear blank line for proper styling -FULLCLEAR = Html("div", class_="fullclear", inline=True) - -# Names for stylesheets -_NARRATIVESCREEN = "narrative-screen.css" -_NARRATIVEPRINT = "narrative-print.css" - -# variables for alphabet_navigation() -_KEYPERSON, _KEYPLACE, _KEYEVENT, _ALPHAEVENT = 0, 1, 2, 3 - -# Web page filename extensions -_WEB_EXT = ['.html', '.htm', '.shtml', '.php', '.php3', '.cgi'] - -_NAME_COL = 3 - _DEFAULT_MAX_IMG_WIDTH = 800 # resize images that are wider than this _DEFAULT_MAX_IMG_HEIGHT = 600 # resize images that are taller than this # The two values above are settable in options. -_WIDTH = 160 -_HEIGHT = 64 -_VGAP = 10 -_HGAP = 30 -_SHADOW = 5 -_XOFFSET = 5 -_WRONGMEDIAPATH = [] - -_NAME_STYLE_SHORT = 2 -_NAME_STYLE_DEFAULT = 1 -_NAME_STYLE_FIRST = 0 -_NAME_STYLE_SPECIAL = None - -PLUGMAN = BasePluginManager.get_instance() -CSS = PLUGMAN.process_plugin_data('WEBSTUFF') - -_HTML_DBL_QUOTES = re.compile(r'([^"]*) " ([^"]*) " (.*)', re.VERBOSE) -_HTML_SNG_QUOTES = re.compile(r"([^']*) ' ([^']*) ' (.*)", re.VERBOSE) - -# 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 = escape(text) - - # Deal with double quotes. - match = _HTML_DBL_QUOTES.match(text) - while match: - text = "%s" "“" "%s" "”" "%s" % match.groups() - match = _HTML_DBL_QUOTES.match(text) - # Replace remaining double quotes. - text = text.replace('"', '"') - - # Deal with single quotes. - text = text.replace("'s ", '’s ') - match = _HTML_SNG_QUOTES.match(text) - while match: - text = "%s" "‘" "%s" "’" "%s" % match.groups() - match = _HTML_SNG_QUOTES.match(text) - # 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.encode('utf-8')).hexdigest() - -def conf_priv(obj): - """ - Return private string - - @param: obj -- The object reference - """ - if obj.get_privacy() != 0: - return ' priv="%d"' % obj.get_privacy() - else: - return '' - -def get_gendex_data(database, event_ref): - """ - Given an event, return the date and place a strings - - @param: database -- The database - @param: event_ref -- The event reference - """ - doe = "" # date of event - poe = "" # place of event - if event_ref and event_ref.ref: - event = database.get_event_from_handle(event_ref.ref) - if event: - date = event.get_date_object() - doe = format_date(date) - if event.get_place_handle(): - place_handle = event.get_place_handle() - if place_handle: - place = database.get_place_from_handle(place_handle) - if place: - poe = _pd.display(database, place, date) - return doe, poe - -def format_date(date): - """ - Format the date - """ - start = date.get_start_date() - if start != Date.EMPTY: - cal = date.get_calendar() - mod = date.get_modifier() - quality = date.get_quality() - if quality in DATE_QUALITY: - qual_text = DATE_QUALITY[quality] + " " - else: - qual_text = "" - if mod == Date.MOD_SPAN: - val = "%sFROM %s TO %s" % ( - qual_text, - make_gedcom_date(start, cal, mod, None), - make_gedcom_date(date.get_stop_date(), cal, mod, None)) - elif mod == Date.MOD_RANGE: - val = "%sBET %s AND %s" % ( - qual_text, - make_gedcom_date(start, cal, mod, None), - make_gedcom_date(date.get_stop_date(), cal, mod, None)) - else: - val = make_gedcom_date(start, cal, mod, quality) - return val - return "" - -# pylint: disable=unused-variable -# pylint: disable=unused-argument - -class BasePage: - """ - Manages all the functions, variables, and everything needed - for all of the classes contained within this plugin - """ - def __init__(self, report, title, gid=None): - """ - @param: report -- The instance of the main report class for - this report - @param: title -- Is the title of the web page - @param: gid -- The family gramps ID - """ - self.uplink = False - # class to do conversion of styled notes to html markup - self._backend = HtmlBackend() - self._backend.build_link = report.build_link - - self.report = report - self.r_db = report.database - self.r_user = report.user - self.title_str = title - self.gid = gid - self.bibli = Bibliography() - - self.page_title = "" - - self.author = get_researcher().get_name() - if self.author: - self.author = self.author.replace(',,,', '') - - # 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. - self.html_dir = report.options['target'] - self.ext = report.options['ext'] - self.noid = report.options['nogid'] - self.linkhome = report.options['linkhome'] - self.create_media = report.options['gallery'] - self.create_unused_media = report.options['unused'] - self.create_thumbs_only = report.options['create_thumbs_only'] - self.inc_families = report.options['inc_families'] - self.inc_events = report.options['inc_events'] - self.usecms = report.options['usecms'] - self.target_uri = report.options['cmsuri'] - self.usecal = report.options['usecal'] - self.target_cal_uri = report.options['caluri'] - self.familymappages = None - lang = report.options['trans'] - self.rlocale = report.set_locale(lang) - self._ = self.rlocale.translation.sgettext - self.COLON = self._(':') # translators: needed for French, else ignore - - if report.options['securesite']: - self.secure_mode = HTTPS - else: - self.secure_mode = HTTP - - # Functions used when no Web Page plugin is provided - def add_instance(self, *param): - """ - Add an instance - """ - pass - - def display_pages(self, title): - """ - Display the pages - """ - pass - - def sort_on_name_and_grampsid(self, handle): - """ Used to sort on name and gramps ID. """ - person = self.r_db.get_person_from_handle(handle) - name = _nd.display(person) - return (name, person.get_gramps_id()) - - def sort_on_grampsid(self, event_ref): - """ - Sort on gramps ID - """ - evt = self.r_db.get_event_from_handle( - event_ref.ref) - return evt.get_gramps_id() - - def copy_thumbnail(self, handle, photo, region=None): - """ - Given a handle (and optional region) make (if needed) an - up-to-date cache of a thumbnail, and call report.copy_file - to copy the cached thumbnail to the website. - Return the new path to the image. - """ - to_dir = self.report.build_path('thumb', handle) - to_path = os.path.join(to_dir, handle) + ( - ('%d,%d-%d,%d.png' % region) if region else '.png' - ) - - if photo.get_mime_type(): - full_path = media_path_full(self.r_db, photo.get_path()) - from_path = get_thumbnail_path(full_path, - photo.get_mime_type(), - region) - if not os.path.isfile(from_path): - from_path = CSS["Document"]["filename"] - else: - from_path = CSS["Document"]["filename"] - self.report.copy_file(from_path, to_path) - return to_path - - def get_nav_menu_hyperlink(self, url_fname, nav_text): - """ - Returns the navigation menu hyperlink - """ - if url_fname == self.target_cal_uri: - uplink = False - else: - uplink = self.uplink - - # check for web page file extension? - if not _has_webpage_extension(url_fname): - url_fname += self.ext - - # get menu item url and begin hyperlink... - url = self.report.build_url_fname(url_fname, None, uplink) - - return Html("a", nav_text, href=url, title=nav_text, inline=True) - - def get_column_data(self, unordered, data_list, column_title): - """ - Returns the menu column for Drop Down Menus and Drop Down Citations - """ - if len(data_list) == 0: - return - - elif len(data_list) == 1: - url_fname, nav_text = data_list[0][0], data_list[0][1] - hyper = self.get_nav_menu_hyperlink(url_fname, nav_text) - unordered.extend( - Html("li", hyper, inline=True) - ) - else: - col_list = Html("li") + ( - Html("a", column_title, href="#", - title=column_title, inline=True) - ) - unordered += col_list - - unordered1 = Html("ul") - col_list += unordered1 - - for url_fname, nav_text in data_list: - hyper = self.get_nav_menu_hyperlink(url_fname, nav_text) - unordered1.extend(Html("li", hyper, inline=True)) - - def display_relationships(self, individual, place_lat_long): - """ - Displays a person's relationships ... - - @param: family_handle_list -- families in this report database - @param: place_lat_long -- for use in Family Map Pages. This will be None - if called from Family pages, which do not create a Family Map - """ - family_list = individual.get_family_handle_list() - if not family_list: - return None - - with Html("div", class_="subsection", id="families") as section: - section += Html("h4", self._("Families"), inline=True) - - table_class = "infolist" - if len(family_list) > 1: - table_class += " fixed_subtables" - with Html("table", class_=table_class) as table: - section += table - - for family_handle in family_list: - family = self.r_db.get_family_from_handle(family_handle) - if family: - link = self.family_link( - family_handle, - self.report.obj_dict[Family][family_handle][1], - gid=family.get_gramps_id(), uplink=True) - trow = Html("tr", class_="BeginFamily") + ( - Html("td", " ", class_="ColumnType", - inline=True), - Html("td", " ", class_="ColumnAttribute", - inline=True), - Html("td", link, class_="ColumnValue", - inline=True) - ) - table += trow - # find the spouse of the principal individual and - # display that person - sp_hdl = utils.find_spouse(individual, family) - if sp_hdl: - spouse = self.r_db.get_person_from_handle(sp_hdl) - if spouse: - table += self.display_spouse(spouse, family, - place_lat_long) - - details = self.display_family_details(family, - place_lat_long) - if details is not None: - table += details - return section - - def display_family_relationships(self, family, place_lat_long): - """ - Displays a family's relationships ... - - @param: family -- the family to be displayed - @param: place_lat_long -- for use in Family Map Pages. This will be None - if called from Family pages, which do not create a Family Map - """ - with Html("div", class_="subsection", id="families") as section: - section += Html("h4", self._("Families"), inline=True) - - table_class = "infolist" - with Html("table", class_=table_class) as table: - section += table - for person_hdl in [family.get_father_handle(), - family.get_mother_handle()]: - person = None - if person_hdl: - person = self.r_db.get_person_from_handle(person_hdl) - if person: - table += self.display_spouse(person, - family, place_lat_long) - - details = self.display_family_details(family, place_lat_long) - if details is not None: - table += details - return section - - def display_family_details(self, family, place_lat_long): - """ - Display details about one family: family events, children, family LDS - ordinances, family attributes - """ - table = None - birthorder = self.report.options["birthorder"] - # display family events; such as marriage and divorce... - family_events = family.get_event_ref_list() - if family_events: - trow = Html("tr") + ( - Html("td", " ", class_="ColumnType", inline=True), - Html("td", " ", class_="ColumnAttribute", inline=True), - Html("td", self.format_family_events(family_events, - place_lat_long), - class_="ColumnValue") - ) - table = trow - - # If the families pages are not output, display family notes - if not self.inc_families: - notelist = family.get_note_list() - for notehandle in notelist: - note = self.r_db.get_note_from_handle(notehandle) - if note: - trow = Html("tr") + ( - Html("td", " ", class_="ColumnType", inline=True), - Html("td", self._("Narrative"), - class_="ColumnAttribute", - inline=True), - Html("td", self.get_note_format(note, True), - class_="ColumnValue") - ) - table = table + trow if table is not None else trow - - childlist = family.get_child_ref_list() - if childlist: - trow = Html("tr") + ( - Html("td", " ", class_="ColumnType", inline=True), - Html("td", self._("Children"), class_="ColumnAttribute", - inline=True) - ) - table = table + trow if table is not None else trow - - tcell = Html("td", class_="ColumnValue", close=False) - trow += tcell - - with Html("table", class_="infolist eventlist") as table2: - thead = Html("thead") - table2 += thead - header = Html("tr") - - header.extend( - Html("th", label, class_=colclass, inline=True) - for (label, colclass) in [ - [self._("Name"), "ColumnName"], - [self._("Birth Date"), "ColumnDate"], - [self._("Death Date"), "ColumnDate"], - ] - ) - thead += header - - # begin table body - tbody = Html("tbody") - table2 += tbody - - childlist = [child_ref.ref for child_ref in childlist] - - # add individual's children event places to family map... - if self.familymappages: - for handle in childlist: - child = self.r_db.get_person_from_handle(handle) - if child: - self._get_event_place(child, place_lat_long) - - children = add_birthdate(self.r_db, childlist, self.rlocale) - if birthorder: - children = sorted(children) - - tbody.extend( - ( - Html("tr", inline=True) + - Html("td", inline=True, close=False) + - self.display_child_link(chandle) + - Html("td", birth, inline=True) + - Html("td", death, inline=True) - ) - for birth_date, birth, death, chandle in children - ) - trow += table2 - - # family LDS ordinance list - family_lds_ordinance_list = family.get_lds_ord_list() - if family_lds_ordinance_list: - trow = Html("tr") + ( - Html("td", " ", class_="ColumnType", inline=True), - Html("td", self._("LDS Ordinance"), class_="ColumnAttribute", - inline=True), - Html("td", self.dump_ordinance(family, "Family"), - class_="ColumnValue") - ) - table = table + trow if table is not None else trow - - # Family Attribute list - family_attribute_list = family.get_attribute_list() - if family_attribute_list: - trow = Html("tr") + ( - Html("td", " ", class_="ColumnType", inline=True), - Html("td", self._("Attributes"), class_="ColumnAttribute", - inline=True) - ) - table = table + trow if table is not None else trow - - tcell = Html("td", class_="ColumnValue") - trow += tcell - - # we do not need the section variable for this instance - # of Attributes... - dummy, attrtable = self.display_attribute_header() - tcell += attrtable - self.display_attr_list(family_attribute_list, attrtable) - return table - - def display_dates(self, handle): - """ - used to display the birth date - - @param: handle -- handle to the person - """ - birth = death = "" - person = self.r_db.get_person_from_handle(handle) - if person: - bd_event = get_birth_or_fallback(self.r_db, child) - if bd_event: - birth = self.rlocale.get_date(bd_event.get_date_object()) - dd_event = get_death_or_fallback(self.r_db, child) - if dd_event: - death = self.rlocale.get_date(dd_event.get_date_object()) - return birth, death - - def complete_people(self, tcell, first_person, handle_list, uplink=True): - """ - completes the person column for classes EventListPage and EventPage - - @param: tcell -- table cell from its caller - @param: first_person -- Not used any more, done via css - @param: handle_list -- handle list from the backlink of the event_handle - """ - for (classname, handle) in handle_list: - - # personal event - if classname == "Person": - tcell += Html("span", self.new_person_link(handle, uplink), - class_="person", inline=True) - - # family event - else: - _obj = self.r_db.get_family_from_handle(handle) - if _obj: - - # husband and spouse in this example, - # are called father and mother - husband_handle = _obj.get_father_handle() - if husband_handle: - hlink = self.new_person_link(husband_handle, uplink) - spouse_handle = _obj.get_mother_handle() - if spouse_handle: - slink = self.new_person_link(spouse_handle, uplink) - - if spouse_handle and husband_handle: - tcell += Html("span", hlink, class_="father", - inline=True) - tcell += Html("span", slink, class_="mother", - inline=True) - elif spouse_handle: - tcell += Html("span", slink, class_="mother", - inline=True) - elif husband_handle: - tcell += Html("span", hlink, class_="father", - inline=True) - return tcell - - def dump_attribute(self, attr): - """ - dump attribute for object presented in display_attr_list() - - @param: attr = attribute object - """ - trow = Html("tr") - - trow.extend( - Html("td", data or " ", class_=colclass, - inline=True if (colclass == "Type" or "Sources") else False) - for (data, colclass) in [ - (str(attr.get_type()), "ColumnType"), - (attr.get_value(), "ColumnValue"), - (self.dump_notes(attr.get_note_list()), "ColumnNotes"), - (self.get_citation_links(attr.get_citation_list()), - "ColumnSources") - ] - ) - return trow - - def get_citation_links(self, citation_handle_list): - """ - get citation link from the citation handle list - - @param: citation_handle_list = list of gen/lib/Citation - """ - text = "" - for citation_handle in citation_handle_list: - citation = self.r_db.get_citation_from_handle(citation_handle) - if citation: - index, key = self.bibli.add_reference(citation) - id_ = "%d%s" % (index+1, key) - text += ' %s' % (id_, id_) - return text - - def get_note_format(self, note, link_prefix_up): - """ - will get the note from the database, and will return either the - styled text or plain note - """ - self.report.link_prefix_up = link_prefix_up - - text = "" - if note is not None: - # retrieve the body of the note - note_text = note.get() - - # styled notes - htmlnotetext = self.styled_note( - note.get_styledtext(), note.get_format(), - contains_html=(note.get_type() == NoteType.HTML_CODE)) - text = htmlnotetext or Html("p", note_text) - - # return text of the note to its callers - return text - - def styled_note(self, styledtext, styled_format, contains_html=False): - """ - styledtext : assumed a StyledText object to write - styled_format : = 0 : Flowed, = 1 : Preformatted - style_name : name of the style to use for default presentation - """ - text = str(styledtext) - - if not text: - return '' - - s_tags = styledtext.get_tags() - htmllist = Html("div", class_="grampsstylednote") - if contains_html: - markuptext = self._backend.add_markup_from_styled(text, - s_tags, - split='\n', - escape=False) - htmllist += markuptext - else: - markuptext = self._backend.add_markup_from_styled(text, - s_tags, - split='\n') - linelist = [] - linenb = 1 - for line in markuptext.split('\n'): - [line, sigcount] = process_spaces(line, styled_format) - if sigcount == 0: - # The rendering of an empty paragraph '

    ' - # is undefined so we use a non-breaking space - if linenb == 1: - linelist.append(' ') - htmllist.extend(Html('p') + linelist) - linelist = [] - linenb = 1 - else: - if linenb > 1: - linelist[-1] += '
    ' - linelist.append(line) - linenb += 1 - if linenb > 1: - htmllist.extend(Html('p') + linelist) - # if the last line was blank, then as well as outputting - # the previous para, which we have just done, - # we also output a new blank para - if sigcount == 0: - linelist = [" "] - htmllist.extend(Html('p') + linelist) - return htmllist - - def dump_notes(self, notelist): - """ - dump out of list of notes with very little elements of its own - - @param: notelist -- list of notes - """ - if not notelist: - return Html("div") - - # begin unordered list - notesection = Html("div") - for notehandle in notelist: - this_note = self.r_db.get_note_from_handle(notehandle) - if this_note is not None: - notesection.extend(Html("i", self._(this_note.type.xml_str()), - class_="NoteType")) - notesection.extend(self.get_note_format(this_note, True)) - return notesection - - def event_header_row(self): - """ - creates the event header row for all events - """ - trow = Html("tr") - trow.extend( - Html("th", trans, class_=colclass, inline=True) - for trans, colclass in [ - (self._("Event"), "ColumnEvent"), - (self._("Date"), "ColumnDate"), - (self._("Place"), "ColumnPlace"), - (self._("Description"), "ColumnDescription"), - (self._("Notes"), "ColumnNotes"), - (self._("Sources"), "ColumnSources")] - ) - return trow - - def display_event_row(self, event, event_ref, place_lat_long, - uplink, hyperlink, omit): - """ - display the event row for IndividualPage - - @param: evt -- Event object from report database - @param: evt_ref -- Event reference - @param: place_lat_long -- For use in Family Map Pages. This will be None - if called from Family pages, which do not - create a Family Map - @param: uplink -- If True, then "../../../" is inserted in front - of the result. - @param: hyperlink -- Add a hyperlink or not - @param: omit -- Role to be omitted in output - """ - event_gid = event.get_gramps_id() - - place_handle = event.get_place_handle() - if place_handle: - place = self.r_db.get_place_from_handle(place_handle) - if place: - self.append_to_place_lat_long(place, event, place_lat_long) - - # begin event table row - trow = Html("tr") - - # get event type and hyperlink to it or not? - etype = self._(event.get_type().xml_str()) - - event_role = event_ref.get_role() - if not event_role == omit: - etype += " (%s)" % event_role - event_hyper = self.event_link(event_ref.ref, - etype, - event_gid, - uplink) if hyperlink else etype - trow += Html("td", event_hyper, class_="ColumnEvent") - - # get event data - event_data = self.get_event_data(event, event_ref, uplink) - - trow.extend( - Html("td", data or " ", class_=colclass, - inline=(not data or colclass == "ColumnDate")) - for (label, colclass, data) in event_data - ) - - # get event notes - notelist = event.get_note_list() - notelist.extend(event_ref.get_note_list()) - htmllist = self.dump_notes(notelist) - - # if the event or event reference has an attribute attached to it, - # get the text and format it correctly? - attrlist = event.get_attribute_list() - attrlist.extend(event_ref.get_attribute_list()) - for attr in attrlist: - htmllist.extend(Html("p", - _("%(str1)s: %(str2)s") % { - 'str1' : Html("b", attr.get_type()), - 'str2' : attr.get_value() - })) - - #also output notes attached to the attributes - notelist = attr.get_note_list() - if notelist: - htmllist.extend(self.dump_notes(notelist)) - - trow += Html("td", htmllist, class_="ColumnNotes") - - # get event source references - srcrefs = self.get_citation_links(event.get_citation_list()) or " " - trow += Html("td", srcrefs, class_="ColumnSources") - - # return events table row to its callers - return trow - - def append_to_place_lat_long(self, place, event, place_lat_long): - """ - Create a list of places with coordinates. - - @param: place_lat_long -- for use in Family Map Pages. This will be None - if called from Family pages, which do not create a Family Map - """ - if place_lat_long is None: - return - place_handle = place.get_handle() - event_date = event.get_date_object() - - # 0 = latitude, 1 = longitude, 2 - placetitle, - # 3 = place handle, 4 = event date, 5 = event type - found = any(data[3] == place_handle and data[4] == event_date - for data in place_lat_long) - if not found: - placetitle = _pd.display(self.r_db, place) - latitude = place.get_latitude() - longitude = place.get_longitude() - if latitude and longitude: - latitude, longitude = conv_lat_lon(latitude, longitude, "D.D8") - if latitude is not None: - etype = event.get_type() - place_lat_long.append([latitude, longitude, placetitle, - place_handle, event_date, etype]) - - def _get_event_place(self, person, place_lat_long): - """ - Retrieve from a person their events, and places for family map - - @param: person -- Person object from the database - @param: place_lat_long -- For use in Family Map Pages. This will be - None if called from Family pages, which do - not create a Family Map - """ - if not person: - return - - # check to see if this person is in the report database? - use_link = self.report.person_in_webreport(person.get_handle()) - if use_link: - evt_ref_list = person.get_event_ref_list() - if evt_ref_list: - for evt_ref in evt_ref_list: - event = self.r_db.get_event_from_handle(evt_ref.ref) - if event: - pl_handle = event.get_place_handle() - if pl_handle: - place = self.r_db.get_place_from_handle(pl_handle) - if place: - self.append_to_place_lat_long(place, event, - place_lat_long) - - def family_link(self, family_handle, name, gid=None, uplink=False): - """ - Create the url and link for FamilyPage - - @param: family_handle -- The handle for the family to link - @param: name -- The family name - @param: gid -- The family gramps ID - @param: uplink -- If True, then "../../../" is inserted in front - of the result. - """ - name = html_escape(name) - if not self.noid and gid: - gid_html = Html("span", " [%s]" % gid, class_="grampsid", - inline=True) - else: - gid_html = "" - - result = self.report.obj_dict.get(Family).get(family_handle) - if result is None: - # the family is not included in the webreport - return name + str(gid_html) - - url = self.report.build_url_fname(result[0], uplink=uplink) - hyper = Html("a", name, href=url, title=name) - hyper += gid_html - return hyper - - def get_family_string(self, family): - """ - Unused method ??? - Returns a hyperlink for each person linked to the Family Page - - @param: family -- The family - """ - husband, spouse = [False]*2 - - husband_handle = family.get_father_handle() - - if husband_handle: - husband = self.r_db.get_person_from_handle(husband_handle) - else: - husband = None - - spouse_handle = family.get_mother_handle() - if spouse_handle: - spouse = self.r_db.get_person_from_handle(spouse_handle) - else: - spouse = None - - if husband: - husband_name = self.get_name(husband) - hlink = self.family_link(family.get_handle(), - husband_name, uplink=self.uplink) - if spouse: - spouse_name = self.get_name(spouse) - slink = self.family_link(family.get_handle(), - spouse_name, uplink=self.uplink) - - title_str = '' - if husband and spouse: - title_str = '%s ' % hlink + self._("and") + ' %s' % slink - elif husband: - title_str = '%s ' % hlink - elif spouse: - title_str = '%s ' % slink - return title_str - - def event_link(self, event_handle, event_title, gid=None, uplink=False): - """ - Creates a hyperlink for an event based on its type - - @param: event_handle -- Event handle - @param: event_title -- Event title - @param: gid -- The gramps ID for the event - @param: uplink -- If True, then "../../../" is inserted in front - of the result. - """ - if not self.inc_events: - return event_title - - url = self.report.build_url_fname_html(event_handle, "evt", uplink) - hyper = Html("a", event_title, href=url, title=event_title) - - if not self.noid and gid: - hyper += Html("span", " [%s]" % gid, class_="grampsid", inline=True) - return hyper - - def format_family_events(self, event_ref_list, place_lat_long): - """ - displays the event row for events such as marriage and divorce - - @param: event_ref_list -- List of events reference - @param: place_lat_long -- For use in Family Map Pages. This will be None - if called from Family pages, which do not - create a Family Map - """ - with Html("table", class_="infolist eventlist") as table: - thead = Html("thead") - table += thead - - # attach event header row - thead += self.event_header_row() - - # begin table body - tbody = Html("tbody") - table += tbody - - for evt_ref in event_ref_list: - event = self.r_db.get_event_from_handle(evt_ref.ref) - - # add event body row - tbody += self.display_event_row(event, evt_ref, place_lat_long, - uplink=True, hyperlink=True, - omit=EventRoleType.FAMILY) - return table - - def get_event_data(self, evt, evt_ref, - uplink, gid=None): - """ - retrieve event data from event and evt_ref - - @param: evt -- Event from database - @param: evt_ref -- Event reference - @param: uplink -- If True, then "../../../" is inserted in front of - the result. - """ - place = None - place_handle = evt.get_place_handle() - if place_handle: - place = self.r_db.get_place_from_handle(place_handle) - - place_hyper = None - if place: - place_name = _pd.display(self.r_db, place, evt.get_date_object()) - place_hyper = self.place_link(place_handle, place_name, - uplink=uplink) - - evt_desc = evt.get_description() - - # wrap it all up and return to its callers - # position 0 = translatable label, position 1 = column class - # position 2 = data - return [(self._("Date"), "ColumnDate", - self.rlocale.get_date(evt.get_date_object())), - (self._("Place"), "ColumnPlace", place_hyper), - (self._("Description"), "ColumnDescription", evt_desc)] - - def dump_ordinance(self, ldsobj, ldssealedtype): - """ - will dump the LDS Ordinance information for either - a person or a family ... - - @param: ldsobj -- Either person or family - @param: ldssealedtype -- Either Sealed to Family or Spouse - """ - objectldsord = ldsobj.get_lds_ord_list() - if not objectldsord: - return None - - # begin LDS ordinance table and table head - with Html("table", class_="infolist ldsordlist") as table: - thead = Html("thead") - table += thead - - # begin HTML row - trow = Html("tr") - thead += trow - - trow.extend( - Html("th", label, class_=colclass, inline=True) - for (label, colclass) in [ - [self._("Type"), "ColumnLDSType"], - [self._("Date"), "ColumnDate"], - [self._("Temple"), "ColumnLDSTemple"], - [self._("Place"), "ColumnLDSPlace"], - [self._("Status"), "ColumnLDSStatus"], - [self._("Sources"), "ColumnLDSSources"] - ] - ) - - # start table body - tbody = Html("tbody") - table += tbody - - for ordobj in objectldsord: - place_hyper = " " - place_handle = ordobj.get_place_handle() - if place_handle: - place = self.r_db.get_place_from_handle(place_handle) - if place: - place_title = _pd.display(self.r_db, place) - place_hyper = self.place_link( - place_handle, place_title, - place.get_gramps_id(), uplink=True) - - # begin ordinance rows - trow = Html("tr") - - trow.extend( - Html("td", value or " ", class_=colclass, - inline=(not value or colclass == "ColumnDate")) - for (value, colclass) in [ - (ordobj.type2xml(), "ColumnType"), - (self.rlocale.get_date(ordobj.get_date_object()), - "ColumnDate"), - (ordobj.get_temple(), "ColumnLDSTemple"), - (place_hyper, "ColumnLDSPlace"), - (ordobj.get_status(), "ColumnLDSStatus"), - (self.get_citation_links(ordobj.get_citation_list()), - "ColumnSources") - ] - ) - tbody += trow - return table - - def write_srcattr(self, srcattr_list): - """ - Writes out the srcattr for the different objects - - @param: srcattr_list -- List of source attributes - """ - if len(srcattr_list) == 0: - return None - - # begin data map division and section title... - with Html("div", class_="subsection", id="data_map") as section: - section += Html("h4", self._("Attributes"), inline=True) - - with Html("table", class_="infolist") as table: - section += table - - thead = Html("thead") - table += thead - - trow = Html("tr") + ( - Html("th", self._("Key"), class_="ColumnAttribute", - inline=True), - Html("th", self._("Value"), class_="ColumnValue", - inline=True) - ) - thead += trow - - tbody = Html("tbody") - table += tbody - - for srcattr in srcattr_list: - trow = Html("tr") + ( - Html("td", str(srcattr.get_type()), - class_="ColumnAttribute", inline=True), - Html("td", srcattr.get_value(), - class_="ColumnValue", inline=True) - ) - tbody += trow - return section - - def source_link(self, source_handle, source_title, - gid=None, cindex=None, uplink=False): - """ - Creates a link to the source object - - @param: source_handle -- Source handle from database - @param: source_title -- Title from the source object - @param: gid -- Source gramps id from the source object - @param: cindex -- Count index - @param: uplink -- If True, then "../../../" is inserted in front - of the result. - """ - url = self.report.build_url_fname_html(source_handle, "src", uplink) - hyper = Html("a", source_title, - href=url, - title=source_title) - - # if not None, add name reference to hyperlink element - if cindex: - hyper.attr += ' name ="sref%d"' % cindex - - # add Gramps ID - if not self.noid and gid: - hyper += Html("span", ' [%s]' % gid, class_="grampsid", inline=True) - return hyper - - def display_addr_list(self, addrlist, showsrc): - """ - Display a person's or repository's addresses ... - - @param: addrlist -- a list of address handles - @param: showsrc -- True = show sources - False = do not show sources - None = djpe - """ - if not addrlist: - return None - - # begin addresses division and title - with Html("div", class_="subsection", id="Addresses") as section: - section += Html("h4", self._("Addresses"), inline=True) - - # write out addresses() - section += self.dump_addresses(addrlist, showsrc) - - # return address division to its caller - return section - - def dump_addresses(self, addrlist, showsrc): - """ - will display an object's addresses, url list, note list, - and source references. - - @param: addrlist = either person or repository address list - @param: showsrc = True -- person and their sources - False -- repository with no sources - None -- Address Book address with sources - """ - if not addrlist: - return None - - # begin summaryarea division - with Html("div", id="AddressTable") as summaryarea: - - # begin address table - with Html("table") as table: - summaryarea += table - - # get table class based on showsrc - if showsrc == True: - table.attr = 'class = "infolist addrlist"' - elif showsrc == False: - table.attr = 'class = "infolist repolist"' - else: - table.attr = 'class = "infolist addressbook"' - - # begin table head - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - addr_header = [ - [self._("Date"), "Date"], - [self._("Street"), "StreetAddress"], - [self._("Locality"), "Locality"], - [self._("City"), "City"], - [self._("State/ Province"), "State"], - [self._("County"), "County"], - [self._("Postal Code"), "Postalcode"], - [self._("Country"), "Cntry"], - [self._("Phone"), "Phone"]] - - # True, False, or None ** see docstring for explanation - if showsrc in [True, None]: - addr_header.append([self._("Sources"), "Sources"]) - - trow.extend( - Html("th", self._(label), - class_="Colummn" + colclass, inline=True) - for (label, colclass) in addr_header - ) - - # begin table body - tbody = Html("tbody") - table += tbody - - # get address list from an object; either repository or person - for address in addrlist: - - trow = Html("tr") - tbody += trow - - addr_data_row = [ - (self.rlocale.get_date(address.get_date_object()), - "ColumnDate"), - (address.get_street(), "ColumnStreetAddress"), - (address.get_locality(), "ColumnLocality"), - (address.get_city(), "ColumnCity"), - (address.get_state(), "ColumnState"), - (address.get_county(), "ColumnCounty"), - (address.get_postal_code(), "ColumnPostalCode"), - (address.get_country(), "ColumnCntry"), - (address.get_phone(), "ColumnPhone") - ] - - # get source citation list - if showsrc in [True, None]: - addr_data_row.append( - [self.get_citation_links( - address.get_citation_list()), - "ColumnSources"]) - - trow.extend( - Html("td", value or " ", - class_=colclass, inline=True) - for (value, colclass) in addr_data_row - ) - - # address: notelist - if showsrc is not None: - notelist = self.display_note_list( - address.get_note_list()) - if notelist is not None: - summaryarea += notelist - return summaryarea - - def addressbook_link(self, person_handle, uplink=False): - """ - Creates a hyperlink for an address book link based on person's handle - - @param: person_handle -- Person's handle from the database - @param: uplink -- If True, then "../../../" is inserted in front - of the result. - """ - url = self.report.build_url_fname_html(person_handle, "addr", uplink) - person = self.r_db.get_person_from_handle(person_handle) - person_name = self.get_name(person) - - # return addressbook hyperlink to its caller - return Html("a", person_name, href=url, title=html_escape(person_name)) - - def get_copyright_license(self, copyright_, uplink=False): - """ - Will return either the text or image of the copyright license - - @param: copyright_ -- The kind of copyright - @param: uplink -- If True, then "../../../" is inserted in front - of the result. - """ - text = '' - if copyright_ == 0: - if self.author: - year = Today().get_year() - text = '© %(year)d %(person)s' % { - 'person' : self.author, - 'year' : year} - elif 0 < copyright_ < len(_CC): - # Note. This is a URL - fname = "/".join(["images", "somerights20.gif"]) - url = self.report.build_url_fname(fname, None, uplink=False) - text = _CC[copyright_] % {'gif_fname' : url} - - # return text or image to its callers - return text - - def get_name(self, person, maiden_name=None): - """ I5118 - - Return person's name, unless maiden_name given, unless married_name - listed. - - @param: person -- person object from database - @param: maiden_name -- Female's family surname - """ - # get name format for displaying names - name_format = self.report.options['name_format'] - - # 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()) == 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 = Name(married_name) - else: - name = Name(primary_name) - surname_obj = name.get_primary_surname() - surname_obj.set_surname(maiden_name) - else: - name = Name(primary_name) - name.set_display_as(name_format) - return _nd.display_name(name) - - def display_attribute_header(self): - """ - Display the attribute section and its table header - """ - # begin attributes division and section title - with Html("div", class_="subsection", id="attributes") as section: - section += Html("h4", self._("Attributes"), inline=True) - - # begin attributes table - with Html("table", class_="infolist attrlist") as attrtable: - section += attrtable - - thead = Html("thead") - attrtable += thead - - trow = Html("tr") - thead += trow - - trow.extend( - Html("th", label, class_=colclass, inline=True) - for (label, colclass) in [ - (self._("Type"), "ColumnType"), - (self._("Value"), "ColumnValue"), - (self._("Notes"), "ColumnNotes"), - (self._("Sources"), "ColumnSources")] - ) - return section, attrtable - - def display_attr_list(self, attrlist, - attrtable): - """ - Will display a list of attributes - - @param: attrlist -- a list of attributes - @param: attrtable -- the table element that is being added to - """ - tbody = Html("tbody") - attrtable += tbody - - tbody.extend( - self.dump_attribute(attr) for attr in attrlist - ) - - def write_footer(self, date): - """ - Will create and display the footer section of each page... - - @param: bottom -- whether to specify location of footer section or not? - """ - # begin footer division - with Html("div", id="footer") as footer: - - footer_note = self.report.options['footernote'] - if footer_note: - note = self.get_note_format( - self.r_db.get_note_from_gramps_id(footer_note), - False - ) - user_footer = Html("div", id='user_footer') - footer += user_footer - - # attach note - user_footer += note - - msg = self._('Generated by %(gramps_home_html_start)s' - 'Gramps%(html_end)s %(version)s' - ) % {'gramps_home_html_start' : - '', - 'html_end' : '', - 'version' : VERSION} - if date is not None: - msg += "
    " - last_modif = datetime.datetime.fromtimestamp(date).strftime( - '%Y-%m-%d %H:%M:%S') - msg += self._('Last change was the %(date)s') % { - 'date' : last_modif} - else: - msg += self._(' on %(date)s') % { - 'date' : self.rlocale.get_date(Today())} - - origin1 = self.report.filter.get_name(self.rlocale) - filt_number = self.report.options['filter'] - # optional "link-home" feature; see bug report #2736 - if self.report.options['linkhome']: - center_person = self.r_db.get_person_from_gramps_id( - self.report.options['pid']) - if (center_person and - self.report.person_in_webreport(center_person.handle)): - center_person_url = self.report.build_url_fname_html( - center_person.handle, "ppl", self.uplink) - - person_name = self.get_name(center_person) - if filt_number > 0 and filt_number < 5: - subject_url = '' - subject_url += origin1 + '' - else: - subject_url = origin1 - msg += self._( - '%(http_break)sCreated for %(subject_url)s') % { - 'http_break' : '
    ', - 'subject_url' : subject_url} - else: - msg += self._( - '%(http_break)sCreated for %(subject_url)s') % { - 'http_break' : '
    ', - 'subject_url' : origin1} - - # creation author - footer += Html("p", msg, id='createdate') - - # get copyright license for all pages - copy_nr = self.report.copyright - - text = '' - if copy_nr == 0: - if self.author: - year = Today().get_year() - text = '© %(year)d %(person)s' % { - 'person' : self.author, 'year' : year} - elif copy_nr < len(_CC): - # Note. This is a URL - fname = "/".join(["images", "somerights20.gif"]) - url = self.report.build_url_fname(fname, None, self.uplink) - text = _CC[copy_nr] % {'gif_fname' : url} - footer += Html("p", text, id='copyright') - - # return footer to its callers - return footer - - def write_header(self, title): - """ - Note. 'title' is used as currentsection in the navigation links and - as part of the header title. - - @param: title -- Is the title of the web page - """ - # begin each html page... - xmllang = xml_lang() - page, head, body = Html.page('%s - %s' % - (html_escape(self.title_str.strip()), - html_escape(title)), - self.report.encoding, - xmllang, cms=self.usecms) - - # temporary fix for .php parsing error - if self.ext in [".php", ".php3", ".cgi"]: - del page[0] - - # Header constants - _meta1 = 'name ="viewport" content="width=device-width; ' - _meta1 += 'height=device-height; initial-scale=0.1; ' - _meta1 += 'maximum-scale=10.0; user-scalable=yes"' - _meta2 = 'name ="apple-mobile-web-app-capable" content="yes"' - _meta3 = 'name="generator" content="%s %s %s"' % ( - PROGRAM_NAME, VERSION, URL_HOMEPAGE) - _meta4 = 'name="author" content="%s"' % self.author - - # create additional meta tags - meta = Html("meta", attr=_meta1) + ( - Html("meta", attr=_meta2, indent=False), - Html("meta", attr=_meta3, indent=False), - Html("meta", attr=_meta4, indent=False) - ) - - # Link to _NARRATIVESCREEN stylesheet - fname = "/".join(["css", _NARRATIVESCREEN]) - url2 = self.report.build_url_fname(fname, None, self.uplink) - - # Link to _NARRATIVEPRINT stylesheet - fname = "/".join(["css", _NARRATIVEPRINT]) - url3 = self.report.build_url_fname(fname, None, self.uplink) - - # Link to Gramps favicon - fname = "/".join(['images', 'favicon2.ico']) - url4 = self.report.build_url_image("favicon2.ico", - "images", self.uplink) - - # create stylesheet and favicon links - links = Html("link", type="image/x-icon", - href=url4, rel="shortcut icon") + ( - Html("link", type="text/css", href=url2, - media="screen", rel="stylesheet", indent=False), - Html("link", type="text/css", href=url3, - media='print', rel="stylesheet", indent=False) - ) - - # Link to Navigation Menus stylesheet - if CSS[self.report.css]["navigation"]: - fname = "/".join(["css", "narrative-menus.css"]) - url = self.report.build_url_fname(fname, None, self.uplink) - links += Html("link", type="text/css", href=url, - media="screen", rel="stylesheet", indent=False) - - # add additional meta and link tags - head += meta - head += links - - # begin header section - headerdiv = Html("div", id='header') + ( - Html("h1", html_escape(self.title_str), id="SiteTitle", inline=True) - ) - body += headerdiv - - header_note = self.report.options['headernote'] - if header_note: - note = self.get_note_format( - self.r_db.get_note_from_gramps_id(header_note), - False) - - user_header = Html("div", id='user_header') - headerdiv += user_header - - # attach note - user_header += note - - # Begin Navigation Menu-- - # is the style sheet either Basic-Blue or Visually Impaired, - # and menu layout is Drop Down? - if (self.report.css == _("Basic-Blue") or - self.report.css == _("Visually Impaired") - ) and self.report.navigation == "dropdown": - body += self.display_drop_menu() - else: - body += self.display_nav_links(title) - - # return page, head, and body to its classes... - return page, head, body - - def display_nav_links(self, currentsection): - """ - Creates the navigation menu - - @param: currentsection = which menu item are you on - """ - # include repositories or not? - inc_repos = True - if (not self.report.inc_repository or - not len(self.r_db.get_repository_handles())): - inc_repos = False - - # create media pages... - _create_media_link = False - if self.create_media: - _create_media_link = True - if self.create_thumbs_only: - _create_media_link = False - - # create link to web calendar pages... - _create_calendar_link = False - if self.usecal: - _create_calendar_link = True - self.target_cal_uri += "/index" - - # Determine which menu items will be available? - # Menu items have been adjusted to concide with Gramps Navigation - # Sidebar order... - - navs = [ - (self.report.index_fname, self._("Html|Home"), - self.report.use_home), - (self.report.intro_fname, self._("Introduction"), - self.report.use_intro), - ('individuals', self._("Individuals"), True), - (self.report.surname_fname, self._("Surnames"), True), - ('families', self._("Families"), self.report.inc_families), - ('events', self._("Events"), self.report.inc_events), - ('places', self._("Places"), True), - ('sources', self._("Sources"), True), - ('repositories', self._("Repositories"), inc_repos), - ('media', self._("Media"), _create_media_link), - ('thumbnails', self._("Thumbnails"), self.create_media), - ('download', self._("Download"), self.report.inc_download), - ("addressbook", self._("Address Book"), - self.report.inc_addressbook), - ('contact', self._("Contact"), self.report.use_contact), - ('statistics', self._("Statistics"), True), - (self.target_cal_uri, self._("Web Calendar"), self.usecal) - ] - - # Remove menu sections if they are not being created? - navs = ((url_text, nav_text) - for url_text, nav_text, cond in navs if cond) - menu_items = [[url, text] for url, text in navs] - - number_items = len(menu_items) - num_cols = 10 - num_rows = ((number_items // num_cols) + 1) - - # begin navigation menu division... - with Html("div", class_="wrapper", - id="nav", role="navigation") as navigation: - with Html("div", class_="container") as container: - - index = 0 - for rows in range(num_rows): - unordered = Html("ul", class_="menu", id="dropmenu") - - cols = 0 - while cols <= num_cols and index < number_items: - url_fname, nav_text = menu_items[index] - - hyper = self.get_nav_menu_hyperlink(url_fname, nav_text) - - # Define 'currentsection' to correctly set navlink item - # CSS id 'CurrentSection' for Navigation styling. - # Use 'self.report.cur_fname' to determine - # 'CurrentSection' for individual elements for - # Navigation styling. - - # Figure out if we need
  • - # or just
  • - - check_cs = False - if nav_text == currentsection: - check_cs = True - elif nav_text == _("Surnames"): - if "srn" in self.report.cur_fname: - check_cs = True - elif _("Surnames") in currentsection: - check_cs = True - elif nav_text == _("Individuals"): - if "ppl" in self.report.cur_fname: - check_cs = True - elif nav_text == _("Families"): - if "fam" in self.report.cur_fname: - check_cs = True - elif nav_text == _("Sources"): - if "src" in self.report.cur_fname: - check_cs = True - elif nav_text == _("Places"): - if "plc" in self.report.cur_fname: - check_cs = True - elif nav_text == _("Events"): - if "evt" in self.report.cur_fname: - check_cs = True - elif nav_text == _("Media"): - if "img" in self.report.cur_fname: - check_cs = True - elif nav_text == _("Address Book"): - if "addr" in self.report.cur_fname: - check_cs = True - temp_cs = 'class = "CurrentSection"' - check_cs = temp_cs if check_cs else False - if check_cs: - unordered.extend( - Html("li", hyper, attr=check_cs, inline=True) - ) - else: - unordered.extend( - Html("li", hyper, inline=True) - ) - index += 1 - cols += 1 - - if rows == num_rows - 1: - prv = Html('%s' % - self._("Previous")) - nxt = Html('%s' % - self._("Next")) - unordered.extend(Html("li", prv, inline=True)) - unordered.extend(Html("li", nxt, inline=True)) - container += unordered - navigation += container - return navigation - - def display_drop_menu(self): - """ - Creates the Drop Down Navigation Menu - """ - # include repositories or not? - inc_repos = True - if (not self.report.inc_repository or - not len(self.r_db.get_repository_handles())): - inc_repos = False - - # create media pages... - _create_media_link = False - if self.create_media: - _create_media_link = True - if self.create_thumbs_only: - _create_media_link = False - - personal = [ - (self.report.intro_fname, self._("Introduction"), - self.report.use_intro), - ("individuals", self._("Individuals"), True), - (self.report.surname_fname, self._("Surnames"), True), - ("families", self._("Families"), self.report.inc_families) - ] - personal = ((url_text, nav_text) - for url_text, nav_text, cond in personal if cond) - personal = [[url, text] for url, text in personal] - - navs1 = [ - ("events", self._("Events"), self.report.inc_events), - ("places", self._("Places"), True), - ("sources", self._("Sources"), True), - ("repositories", self._("Repositories"), inc_repos) - ] - navs1 = ((url_text, nav_text) - for url_text, nav_text, cond in navs1 if cond) - navs1 = [[url, text] for url, text in navs1] - - media = [ - ("media", self._("Media"), _create_media_link), - ("thumbnails", self._("Thumbnails"), True) - ] - media = ((url_text, nav_text) - for url_text, nav_text, cond in media if cond) - media = [[url, text] for url, text in media] - - misc = [ - ('download', self._("Download"), self.report.inc_download), - ("addressbook", self._("Address Book"), self.report.inc_addressbook) - ] - misc = ((url_text, nav_text) - for url_text, nav_text, cond in misc if cond) - misc = [[url, text] for url, text in misc] - - contact = [ - ('contact', self._("Contact"), self.report.use_contact) - ] - contact = ((url_text, nav_text) - for url_text, nav_text, cond in contact if cond) - contact = [[url, text] for url, text in contact] - - # begin navigation menu division... - with Html("div", class_="wrapper", - id="nav", role="navigation") as navigation: - with Html("div", class_="container") as container: - unordered = Html("ul", class_="menu", id="dropmenu") - - if self.report.use_home: - list_html = Html("li", - self.get_nav_menu_hyperlink( - self.report.index_fname, - self._("Html|Home"))) - unordered += list_html - - # add personal column - self.get_column_data(unordered, personal, self._("Personal")) - - if len(navs1): - for url_fname, nav_text in navs1: - unordered.extend( - Html("li", self.get_nav_menu_hyperlink(url_fname, - nav_text), - inline=True) - ) - - # add media column - self.get_column_data(unordered, media, self._("Media")) - - # add miscellaneous column - self.get_column_data(unordered, misc, self._("Miscellaneous")) - - # add contact column - self.get_column_data(unordered, contact, _("Contact")) - - container += unordered - navigation += container - return navigation - - def add_image(self, option_name, height=0): - """ - Will add an image (if present) to the page - - @param: option_name -- The name of the report option - @param: height -- Height of the image - """ - pic_id = self.report.options[option_name] - if pic_id: - obj = self.r_db.get_media_from_gramps_id(pic_id) - if obj is None: - return None - mime_type = obj.get_mime_type() - if mime_type and mime_type.startswith("image"): - try: - - newpath, thumb_path = self.report.prepare_copy_media(obj) - self.report.copy_file(media_path_full( - self.r_db, obj.get_path()), newpath) - - # begin image - image = Html("img") - image.attr = '' - if height: - image.attr += 'height = "%d"' % height - - descr = html_escape(obj.get_description()) - newpath = self.report.build_url_fname(newpath) - image.attr += ' src = "%s" alt = "%s"' % (newpath, descr) - - # return an image - return image - - except (IOError, OSError) as msg: - self.r_user.warn(_("Could not add photo to page"), - str(msg)) - - # no image to return - return None - - def media_ref_rect_regions(self, handle): - """ - 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 - - @param: handle -- The media handle to use - """ - # 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 self.r_db.find_backlink_handles( - handle, - include_classes=["Person", "Family", "Event", "Place"]): - - # 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": - # Is this a person for whom we have built a page: - if self.report.person_in_webreport(newhandle): - # If so, let's add a link to them: - _obj = self.r_db.get_person_from_handle(newhandle) - if _obj: - # What is the shortest possible name we could use - # for this person? - _name = (_obj.get_primary_name().get_call_name() or - _obj.get_primary_name().get_first_name() or - self._("Unknown") - ) - _linkurl = self.report.build_url_fname_html(_obj.handle, - "ppl", True) - elif classname == "Family": - _obj = self.r_db.get_family_from_handle(newhandle) - partner1_handle = _obj.get_father_handle() - partner2_handle = _obj.get_mother_handle() - partner1 = None - partner2 = None - if partner1_handle: - partner1 = self.r_db.get_person_from_handle( - partner1_handle) - if partner2_handle: - partner2 = self.r_db.get_person_from_handle( - partner2_handle) - if partner2 and partner1: - _name = partner1.get_primary_name().get_first_name() - _linkurl = self.report.build_url_fname_html(partner1_handle, - "ppl", True) - elif partner1: - _name = partner1.get_primary_name().get_first_name() - _linkurl = self.report.build_url_fname_html(partner1_handle, - "ppl", True) - elif partner2: - _name = partner2.get_primary_name().get_first_name() - _linkurl = self.report.build_url_fname_html(partner2_handle, - "ppl", True) - if not _name: - _name = self._("Unknown") - elif classname == "Event": - _obj = self.r_db.get_event_from_handle(newhandle) - _name = _obj.get_description() - if not _name: - _name = self._("Unknown") - _linkurl = self.report.build_url_fname_html(_obj.handle, - "evt", True) - elif classname == "Place": - _obj = self.r_db.get_place_from_handle(newhandle) - _name = _pd.display(self.r_db, _obj) - if not _name: - _name = self._("Unknown") - _linkurl = self.report.build_url_fname_html(newhandle, - "plc", True) - - # continue looking through the loop for an object... - if _obj is None: - continue - - # get a list of all media refs for this object - media_list = _obj.get_media_list() - - # go media refs looking for one that points to this image - for mediaref in media_list: - - # is this mediaref for this image? do we have a rect? - if mediaref.ref == handle and mediaref.rect is not None: - - (coord_x1, coord_y1, coord_x2, coord_y2) = mediaref.rect - # Gramps gives us absolute coordinates, - # but we need relative width + height - width = coord_x2 - coord_x1 - height = coord_y2 - coord_y1 - - # remember all this information, cause we'll need - # need it later when we output the
  • ...
  • tags - item = (_name, coord_x1, coord_y1, width, height, _linkurl) - _region_items.add(item) - - # End of code that looks for and prepares the media object regions - - # return media rectangles to its callers - # bug 8950 : it seems it's better to sort on name - # + coords of the rectangle. - def sort_by_name_and_rectangle(obj): - """ - Sort by name and rectangle - - @param: obj -- The object reference - """ - return(obj[0], obj[1], obj[2], obj[3], obj[4]) - - return sorted(_region_items, - key=lambda x: sort_by_name_and_rectangle(x)) - - def media_ref_region_to_object(self, media_handle, obj): - """ - Return a region of this image if it refers to this object. - - @param: media_handle -- The media handle to use - @param: obj -- The object reference - """ - # get a list of all media refs for this object - for mediaref in obj.get_media_list(): - # is this mediaref for this image? do we have a rect? - if (mediaref.ref == media_handle and - mediaref.rect is not None): - return mediaref.rect # (x1, y1, x2, y2) - return None - - def disp_first_img_as_thumbnail(self, photolist, object_): - """ - Return the Html of the first image of photolist that is - associated with object. First image might be a region in an - image. Or, the first image might have regions defined in it. - - @param: photolist -- The list of media - @param: object_ -- The object reference - """ - if not photolist or not self.create_media: - return None - - photo_handle = photolist[0].get_reference_handle() - photo = self.r_db.get_media_from_handle(photo_handle) - mime_type = photo.get_mime_type() - descr = photo.get_description() - - # begin snapshot division - with Html("div", class_="snapshot") as snapshot: - - if mime_type: - - region = self.media_ref_region_to_object(photo_handle, object_) - if region: - - # make a thumbnail of this region - newpath = self.copy_thumbnail(photo_handle, photo, region) - newpath = self.report.build_url_fname(newpath, uplink=True) - - snapshot += self.media_link(photo_handle, newpath, descr, - uplink=self.uplink, - usedescr=False) - else: - - real_path, newpath = self.report.prepare_copy_media(photo) - newpath = self.report.build_url_fname(newpath, uplink=True) - - # FIXME: There doesn't seem to be any point in highlighting - # a sub-region in the thumbnail and linking back to the - # person or whatever. First it is confusing when the link - # probably has nothing to do with the page on which the - # thumbnail is displayed, and second on a thumbnail it is - # probably too small to see, and third, on the thumbnail, - # the link is shown above the image (which is pretty - # useless!) - _region_items = self.media_ref_rect_regions(photo_handle) - if len(_region_items): - with Html("div", id="GalleryDisplay") as mediadisplay: - snapshot += mediadisplay - - ordered = Html("ol", class_="RegionBox") - mediadisplay += ordered - while len(_region_items): - (name, coord_x, coord_y, - width, height, linkurl) = _region_items.pop() - ordered += Html("li", - style="left:%d%%; top:%d%%; " - "width:%d%%; height:%d%%;" % ( - coord_x, coord_y, - width, height)) - ordered += Html("a", name, href=linkurl) - # Need to add link to mediadisplay to get the links: - mediadisplay += self.media_link(photo_handle, - newpath, descr, - self.uplink, False) - else: - try: - - # Begin hyperlink. Description is given only for - # the purpose of the alt tag in img element - snapshot += self.media_link(photo_handle, newpath, - descr, - uplink=self.uplink, - usedescr=False) - - except (IOError, OSError) as msg: - self.r_user.warn(_("Could not add photo to page"), - str(msg)) - else: - # begin hyperlink - snapshot += self.doc_link(photo_handle, descr, - uplink=self.uplink, usedescr=False) - - # return snapshot division to its callers - return snapshot - - def disp_add_img_as_gallery(self, photolist, object_): - """ - Display additional image as gallery - - @param: photolist -- The list of media - @param: object_ -- The object reference - """ - if not photolist or not self.create_media: - return None - - # make referenced images have the same order as in media list: - photolist_handles = {} - for mediaref in photolist: - photolist_handles[mediaref.get_reference_handle()] = mediaref - photolist_ordered = [] - for photoref in copy.copy(object_.get_media_list()): - if photoref.ref in photolist_handles: - photo = photolist_handles[photoref.ref] - photolist_ordered.append(photo) - try: - photolist.remove(photo) - except ValueError: - LOG.warning("Error trying to remove '%s' from photolist", - photo) - # and add any that are left (should there be any?) - photolist_ordered += photolist - - # begin individualgallery division and section title - with Html("div", class_="subsection", id="indivgallery") as section: - section += Html("h4", self._("Media"), inline=True) - - displayed = [] - for mediaref in photolist_ordered: - - photo_handle = mediaref.get_reference_handle() - photo = self.r_db.get_media_from_handle(photo_handle) - - if photo_handle in displayed: - continue - mime_type = photo.get_mime_type() - - # get media description - descr = photo.get_description() - - if mime_type: - try: - # create thumbnail url - # extension needs to be added as it is not already there - url = self.report.build_url_fname(photo_handle, "thumb", - True) + ".png" - # begin hyperlink - section += self.media_link(photo_handle, url, - descr, uplink=self.uplink, - usedescr=True) - except (IOError, OSError) as msg: - self.r_user.warn(_("Could not add photo to page"), - str(msg)) - else: - try: - # begin hyperlink - section += self.doc_link(photo_handle, descr, - uplink=self.uplink) - except (IOError, OSError) as msg: - self.r_user.warn(_("Could not add photo to page"), - str(msg)) - displayed.append(photo_handle) - - # add fullclear for proper styling - section += FULLCLEAR - - # return indivgallery division to its caller - return section - - def display_note_list(self, notelist=None): - """ - Display note list - - @param: notelist -- The list of notes - """ - if not notelist: - return None - - # begin narrative division - with Html("div", class_="subsection narrative") as section: - - for notehandle in notelist: - note = self.r_db.get_note_from_handle(notehandle) - - if note: - note_text = self.get_note_format(note, True) - - # add section title - section += Html("h4", self._("Narrative"), inline=True) - - # attach note - section += note_text - - # return notes to its callers - return section - - def display_url_list(self, urllist=None): - """ - Display URL list - - @param: urllist -- The list of urls - """ - if not urllist: - return None - - # begin web links division - with Html("div", class_="subsection", id="WebLinks") as section: - section += Html("h4", self._("Web Links"), inline=True) - - with Html("table", class_="infolist weblinks") as table: - section += table - - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - trow.extend( - Html('th', label, class_=colclass, inline=True) - for (label, colclass) in [ - (self._("Type"), "ColumnType"), - (self._("Description"), "ColumnDescription") - ] - ) - - tbody = Html("tbody") - table += tbody - - for url in urllist: - - trow = Html("tr") - tbody += trow - - _type = self._(url.get_type().xml_str()) - uri = url.get_path() - descr = url.get_description() - - # Email address - if _type == UrlType.EMAIL: - if not uri.startswith("mailto:"): - uri = "mailto:%(email)s" % {'email' : uri} - - # Web Site address - elif _type == UrlType.WEB_HOME: - if not (uri.startswith("http://") or - uri.startswith("https://")): - url = self.secure_mode - uri = url + "%(website)s" % {"website" : uri} - - # FTP server address - elif _type == UrlType.WEB_FTP: - if not (uri.startswith("ftp://") or - uri.startswith("ftps://")): - uri = "ftp://%(ftpsite)s" % {"ftpsite" : uri} - - descr = Html("p", html_escape(descr)) + ( - Html("a", self._(" [Click to Go]"), href=uri, title=uri) - ) - - trow.extend( - Html("td", data, class_=colclass, inline=True) - for (data, colclass) in [ - (str(_type), "ColumnType"), - (descr, "ColumnDescription") - ] - ) - return section - - def display_lds_ordinance(self, db_obj_): - """ - Display LDS information for a person or family - - @param: db_obj_ -- The database object - """ - ldsordlist = db_obj_.lds_ord_list - if not ldsordlist: - return None - - # begin LDS Ordinance division and section title - with Html("div", class_="subsection", id="LDSOrdinance") as section: - section += Html("h4", _("Latter-Day Saints/ LDS Ordinance"), - inline=True) - - # ump individual LDS ordinance list - section += self.dump_ordinance(db_obj_, "Person") - - # return section to its caller - return section - - def display_ind_sources(self, srcobj): - """ - Will create the "Source References" section for an object - - @param: srcobj -- Sources object - """ - list(map( - lambda i: self.bibli.add_reference( - self.r_db.get_citation_from_handle(i)), - srcobj.get_citation_list())) - sourcerefs = self.display_source_refs(self.bibli) - - # return to its callers - return sourcerefs - - # Only used in IndividualPage.display_ind_sources(), - # and MediaPage.display_media_sources() - def display_source_refs(self, bibli): - """ - Display source references - - @param: bibli -- List of sources - """ - if bibli.get_citation_count() == 0: - return None - - with Html("div", class_="subsection", id="sourcerefs") as section: - section += Html("h4", self._("Source References"), inline=True) - - ordered = Html("ol") - - cindex = 0 - citationlist = bibli.get_citation_list() - for citation in citationlist: - cindex += 1 - # Add this source and its references to the page - source = self.r_db.get_source_from_handle( - citation.get_source_handle()) - if source is not None: - if source.get_author(): - authorstring = source.get_author() + ": " - else: - authorstring = "" - list_html = Html("li", - self.source_link( - source.get_handle(), - authorstring + source.get_title(), - source.get_gramps_id(), cindex, - uplink=self.uplink)) - else: - list_html = Html("li", "None") - - ordered1 = Html("ol") - citation_ref_list = citation.get_ref_list() - for key, sref in citation_ref_list: - cit_ref_li = Html("li", id="sref%d%s" % (cindex, key)) - tmp = Html("ul") - conf = conf_strings.get(sref.confidence, self._('Unknown')) - if conf == conf_strings[Citation.CONF_NORMAL]: - conf = None - else: - conf = _(conf) - for (label, data) in [[self._("Date"), - self.rlocale.get_date(sref.date)], - [self._("Page"), sref.page], - [self._("Confidence"), conf]]: - if data: - tmp += Html("li", - _("%(str1)s: %(str2)s") % { - 'str1' : label, - 'str2' : data - }) - if self.create_media: - for media_ref in sref.get_media_list(): - media_handle = media_ref.get_reference_handle() - media = self.r_db.get_media_from_handle( - media_handle) - if media: - mime_type = media.get_mime_type() - if mime_type: - if mime_type.startswith("image/"): - real_path, new_path = \ - self.report.prepare_copy_media( - media) - newpath = self.report.build_url_fname( - new_path, uplink=self.uplink) - self.report.copy_file( - media_path_full(self.r_db, - media.get_path()), - new_path) - - tmp += Html("li", - self.media_link( - media_handle, - newpath, - media.get_description(), - self.uplink, - usedescr=False), - inline=True) - - else: - tmp += Html("li", - self.doc_link( - media_handle, - media.get_description(), - self.uplink, - usedescr=False), - inline=True) - for handle in sref.get_note_list(): - this_note = self.r_db.get_note_from_handle(handle) - if this_note is not None: - format = self.get_note_format(this_note, True) - tmp += Html("li", - _("%(str1)s: %(str2)s") % { - 'str1' : str(this_note.get_type()), - 'str2' : format - }) - if tmp: - cit_ref_li += tmp - ordered1 += cit_ref_li - - if citation_ref_list: - list_html += ordered1 - ordered += list_html - section += ordered - - # return section to its caller - return section - - def display_references(self, handlelist, - uplink=False): - """ - Display references for the current objects - - @param: handlelist -- List of handles - @param: uplink -- If True, then "../../../" is inserted in front of - the result. - """ - if not handlelist: - return None - - # begin references division and title - with Html("div", class_="subsection", id="references") as section: - section += Html("h4", self._("References"), inline=True) - - ordered = Html("ol") - section += ordered - sortlist = sorted(handlelist, - key=lambda x: self.rlocale.sort_key(x[1])) - - for (path, name, gid) in sortlist: - list_html = Html("li") - ordered += list_html - - name = name or self._("Unknown") - if not self.noid and gid != "": - gid_html = Html("span", " [%s]" % gid, class_="grampsid", - inline=True) - else: - gid_html = "" - - if path != "": - url = self.report.build_url_fname(path, None, self.uplink) - list_html += Html("a", href=url) + name + gid_html - else: - list_html += name + str(gid_html) - - # return references division to its caller - return section - - def family_map_link(self, handle, url): - """ - Creates a link to the family map - - @param: handle -- The family handle - @param: url -- url to be linked - """ - return Html("a", self._("Family Map"), href=url, - title=self._("Family Map"), class_="familymap", inline=True) - - def display_spouse(self, partner, family, place_lat_long): - """ - Display an individual's partner - - @param: partner -- The partner - @param: family -- The family - @param: place_lat_long -- For use in Family Map Pages. This will be None - if called from Family pages, which do not - create a Family Map - """ - gender = partner.get_gender() - reltype = family.get_relationship() - - rtype = self._(str(family.get_relationship().xml_str())) - - if reltype == FamilyRelType.MARRIED: - if gender == Person.FEMALE: - relstr = self._("Wife") - elif gender == Person.MALE: - relstr = self._("Husband") - else: - relstr = self._("Partner") - else: - relstr = self._("Partner") - - # display family relationship status, and add spouse to FamilyMapPages - if self.familymappages: - self._get_event_place(partner, place_lat_long) - - trow = Html("tr", class_="BeginFamily") + ( - Html("td", rtype, class_="ColumnType", inline=True), - Html("td", relstr, class_="ColumnAttribute", inline=True) - ) - - tcell = Html("td", class_="ColumnValue") - trow += tcell - - tcell += self.new_person_link(partner.get_handle(), uplink=True, - person=partner) - birth = death = "" - bd_event = get_birth_or_fallback(self.r_db, partner) - if bd_event: - birth = self.rlocale.get_date(bd_event.get_date_object()) - dd_event = get_death_or_fallback(self.r_db, partner) - if dd_event: - death = self.rlocale.get_date(dd_event.get_date_object()) - - if death == "": - death = "..." - tcell += " ( * ", birth, " + ", death, " )" - - return trow - - def display_child_link(self, chandle): - """ - display child link ... - - @param: chandle -- Child handle - """ - return self.new_person_link(chandle, uplink=True) - - def new_person_link(self, person_handle, uplink=False, person=None, - name_style=_NAME_STYLE_DEFAULT): - """ - creates a link for a person. If a page is generated for the person, a - hyperlink is created, else just the name of the person. The returned - vale will be an Html object if a hyperlink is generated, otherwise just - a string - - @param: person_handle -- Person in database - @param: uplink -- If True, then "../../../" is inserted in front - of the result - @param: person -- Person object. This does not need to be passed. - It should be passed if the person object has - already been retrieved, as it will be used to - improve performance - """ - result = self.report.obj_dict.get(Person).get(person_handle) - - # construct link, name and gid - if result is None: - # The person is not included in the webreport - link = "" - if person is None: - person = self.r_db.get_person_from_handle(person_handle) - if person: - name = self.report.get_person_name(person) - gid = person.get_gramps_id() - else: - name = _("Unknown") - gid = "" - else: - # The person has been encountered in the web report, but this does - # not necessarily mean that a page has been generated - (link, name, gid) = result - - if name_style == _NAME_STYLE_FIRST and person: - name = _get_short_name(person.get_gender(), - person.get_primary_name()) - name = html_escape(name) - # construct the result - if not self.noid and gid != "": - gid_html = Html("span", " [%s]" % gid, class_="grampsid", - inline=True) - else: - gid_html = "" - - if link != "": - url = self.report.build_url_fname(link, uplink=uplink) - hyper = Html("a", name, gid_html, href=url, inline=True) - else: - hyper = name + str(gid_html) - - return hyper - - def media_link(self, media_handle, img_url, name, - uplink=False, usedescr=True): - """ - creates and returns a hyperlink to the thumbnail image - - @param: media_handle -- Photo handle from report database - @param: img_url -- Thumbnail url - @param: name -- Photo description - @param: uplink -- If True, then "../../../" is inserted in front - of the result. - @param: usedescr -- Add media description - """ - url = self.report.build_url_fname_html(media_handle, "img", uplink) - name = html_escape(name) - - # begin thumbnail division - with Html("div", class_="thumbnail") as thumbnail: - - # begin hyperlink - if not self.create_thumbs_only: - hyper = Html("a", href=url, title=name) + ( - Html("img", src=img_url, alt=name) - ) - else: - hyper = Html("img", src=img_url, alt=name) - thumbnail += hyper - - if usedescr: - hyper += Html("p", name, inline=True) - return thumbnail - - def doc_link(self, handle, name, uplink=False, usedescr=True): - """ - create a hyperlink for the media object and returns it - - @param: handle -- Document handle - @param: name -- Document name - @param: uplink -- If True, then "../../../" is inserted in front of - the result. - @param: usedescr -- Add description to hyperlink - """ - url = self.report.build_url_fname_html(handle, "img", uplink) - name = html_escape(name) - - # begin thumbnail division - with Html("div", class_="thumbnail") as thumbnail: - document_url = self.report.build_url_image("document.png", - "images", uplink) - - if not self.create_thumbs_only: - document_link = Html("a", href=url, title=name) + ( - Html("img", src=document_url, alt=name) - ) - else: - document_link = Html("img", src=document_url, alt=name) - - if usedescr: - document_link += Html('br') + ( - Html("span", name, inline=True) - ) - thumbnail += document_link - return thumbnail - - def place_link(self, handle, name, gid=None, uplink=False): - """ - Returns a hyperlink for place link - - @param: handle -- repository handle from report database - @param: name -- repository title - @param: gid -- gramps id - @param: uplink -- If True, then "../../../" is inserted in front of the - result. - """ - url = self.report.build_url_fname_html(handle, "plc", uplink) - - hyper = Html("a", html_escape(name), href=url, title=html_escape(name)) - if not self.noid and gid: - hyper += Html("span", " [%s]" % gid, class_="grampsid", inline=True) - - # return hyperlink to its callers - return hyper - - def dump_place(self, place, table): - """ - Dump a place's information from within the database - - @param: place -- Place object from the database - @param: table -- Table from Placedetail - """ - if place in self.report.visited: - return - self.report.visited.append(place) - # add table body - tbody = Html("tbody") - table += tbody - - gid = place.gramps_id - if not self.noid and gid: - trow = Html("tr") + ( - Html("td", self._("Gramps ID"), class_="ColumnAttribute", - inline=True), - Html("td", gid, class_="ColumnValue", inline=True) - ) - tbody += trow - - data = place.get_latitude() - if data != "": - trow = Html('tr') + ( - Html("td", self._("Latitude"), class_="ColumnAttribute", - inline=True), - Html("td", data, class_="ColumnValue", inline=True) - ) - tbody += trow - data = place.get_longitude() - if data != "": - trow = Html('tr') + ( - Html("td", self._("Longitude"), class_="ColumnAttribute", - inline=True), - Html("td", data, class_="ColumnValue", inline=True) - ) - tbody += trow - - mlocation = get_main_location(self.r_db, place) - for (label, data) in [ - (self._("Street"), mlocation.get(PlaceType.STREET, '')), - (self._("Locality"), mlocation.get(PlaceType.LOCALITY, '')), - (self._("City"), mlocation.get(PlaceType.CITY, '')), - (self._("Church Parish"), mlocation.get(PlaceType.PARISH, '')), - (self._("County"), mlocation.get(PlaceType.COUNTY, '')), - (self._("State/ Province"), mlocation.get(PlaceType.STATE, '')), - (self._("Postal Code"), place.get_code()), - (self._("Country"), mlocation.get(PlaceType.COUNTRY, ''))]: - if data: - trow = Html("tr") + ( - Html("td", label, class_="ColumnAttribute", inline=True), - Html("td", data, class_="ColumnValue", inline=True) - ) - tbody += trow - - altloc = place.get_alternate_locations() - if altloc: - tbody += Html("tr") + Html("td", " ", colspan=2) - trow = Html("tr") + ( - Html("th", self._("Alternate Locations"), colspan=2, - class_="ColumnAttribute", inline=True), - ) - tbody += trow - for loc in (nonempt - for nonempt in altloc if not nonempt.is_empty()): - for (label, data) in [ - (self._("Street"), loc.street), - (self._("Locality"), loc.locality), - (self._("City"), loc.city), - (self._("Church Parish"), loc.parish), - (self._("County"), loc.county), - (self._("State/ Province"), loc.state), - (self._("Postal Code"), loc.postal), - (self._("Country"), loc.country),]: - if data: - trow = Html("tr") + ( - Html("td", label, class_="ColumnAttribute", - inline=True), - Html("td", data, class_="ColumnValue", inline=True) - ) - tbody += trow - tbody += Html("tr") + Html("td", " ", colspan=2) - - # display all related locations - for placeref in place.get_placeref_list(): - place_date = get_date(placeref) - if place_date != "": - parent_place = self.r_db.get_place_from_handle(placeref.ref) - parent_name = parent_place.get_name().get_value() - trow = Html('tr') + ( - Html("td", self._("Locations"), class_="ColumnAttribute", - inline=True), - Html("td", parent_name, class_="ColumnValue", inline=True), - Html("td", place_date, class_="ColumnValue", inline=True) - ) - tbody += trow - - # return place table to its callers - return table - - def repository_link(self, repository_handle, name, gid=None, uplink=False): - """ - Returns a hyperlink for repository links - - @param: repository_handle -- repository handle from report database - @param: name -- repository title - @param: gid -- gramps id - @param: uplink -- If True, then "../../../" is inserted in - front of the result. - """ - url = self.report.build_url_fname_html(repository_handle, - 'repo', uplink) - name = html_escape(name) - - hyper = Html("a", name, href=url, title=name) - - if not self.noid and gid: - hyper += Html("span", '[%s]' % gid, class_="grampsid", inline=True) - return hyper - - def dump_repository_ref_list(self, repo_ref_list): - """ - Dumps the repository - - @param: repo_ref_list -- The list of repositories references - """ - if len(repo_ref_list) == 0: - return None - # Repository list division... - with Html("div", class_="subsection", - id="repositories") as repositories: - repositories += Html("h4", self._("Repositories"), inline=True) - - with Html("table", class_="infolist") as table: - repositories += table - - thead = Html("thead") - table += thead - - trow = Html("tr") + ( - Html("th", self._("Number"), class_="ColumnRowLabel", - inline=True), - Html("th", self._("Title"), class_="ColumnName", - inline=True), - Html("th", self._("Type"), class_="ColumnName", - inline=True), - Html("th", self._("Call number"), class_="ColumnName", - inline=True) - ) - thead += trow - - tbody = Html("tbody") - table += tbody - - index = 1 - for repo_ref in repo_ref_list: - repo = self.r_db.get_repository_from_handle(repo_ref.ref) - if repo: - trow = Html("tr") + ( - Html("td", index, class_="ColumnRowLabel", - inline=True), - Html("td", - self.repository_link(repo_ref.ref, - repo.get_name(), - repo.get_gramps_id(), - self.uplink)), - Html("td", - self._(repo_ref.get_media_type().xml_str()), - class_="ColumnName"), - Html("td", repo_ref.get_call_number(), - class_="ColumnName") - ) - tbody += trow - index += 1 - return repositories - - def dump_residence(self, has_res): - """ - Creates a residence from the database - - @param: has_res -- The residence to use - """ - if not has_res: - return None - - # begin residence division - with Html("div", class_="content Residence") as residence: - residence += Html("h4", self._("Residence"), inline=True) - - with Html("table", class_="infolist place") as table: - residence += table - - place_handle = has_res.get_place_handle() - if place_handle: - place = self.r_db.get_place_from_handle(place_handle) - if place: - self.dump_place(place, table) - - descr = has_res.get_description() - if descr: - - trow = Html("tr") - if len(table) == 3: - # append description row to tbody element of dump_place - table[-2] += trow - else: - # append description row to table element - table += trow - - trow.extend(Html("td", self._("Description"), - class_="ColumnAttribute", inline=True)) - trow.extend(Html("td", descr, class_="ColumnValue", - inline=True)) - - # return information to its callers - return residence - - def display_bkref(self, bkref_list, depth): - """ - Display a reference list for an object class - - @param: bkref_list -- The reference list - @param: depth -- The style of list to use - """ - list_style = "1", "a", "I", "A", "i" - ordered = Html("ol", class_="Col1", role="Volume-n-Page") - ordered.attr += " type=%s" % list_style[depth] - if depth > len(list_style): - return "" - # Sort by the name of the object at the bkref_class, bkref_handle - # bug 8950 : it seems it's better to sort on name + gid. - def sort_by_name_and_gid(obj): - """ - Sort by name then gramps ID - """ - return (obj[1], obj[2]) - - for (bkref_class, bkref_handle) in sorted( - bkref_list, key=lambda x: - sort_by_name_and_gid(self.report.obj_dict[x[0]][x[1]])): - list_html = Html("li") - path = self.report.obj_dict[bkref_class][bkref_handle][0] - name = self.report.obj_dict[bkref_class][bkref_handle][1] - gid = self.report.obj_dict[bkref_class][bkref_handle][2] - ordered += list_html - if path == "": - list_html += name - list_html += self.display_bkref( - self.report.bkref_dict[bkref_class][bkref_handle], - depth+1) - else: - url = self.report.build_url_fname(path, uplink=self.uplink) - if not self.noid and gid != "": - gid_html = Html("span", " [%s]" % gid, - class_="grampsid", inline=True) - else: - gid_html = "" - list_html += Html("a", href=url) + name + gid_html - return ordered - - def display_bkref_list(self, obj_class, obj_handle): - """ - Display a reference list for an object class - - @param: obj_class -- The object class to use - @param: obj_handle -- The handle to use - """ - bkref_list = self.report.bkref_dict[obj_class][obj_handle] - if not bkref_list: - return None - # begin references division and title - with Html("div", class_="subsection", id="references") as section: - section += Html("h4", self._("References"), inline=True) - depth = 0 - ordered = self.display_bkref(bkref_list, depth) - section += ordered - return section - - # ------------------------------------------------------------------------- - # # Web Page Fortmatter and writer - # ------------------------------------------------------------------------- - def xhtml_writer(self, htmlinstance, output_file, sio, date): - """ - Will format, write, and close the file - - @param: output_file -- Open file that is being written to - @param: htmlinstance -- Web page created with libhtml - src/plugins/lib/libhtml.py - """ - htmlinstance.write(partial(print, file=output_file)) - - # closes the file - self.report.close_file(output_file, sio, date) - -################################################# -# -# create the page from SurnameListPage -# -################################################# -class SurnamePage(BasePage): - """ - This will create a list of individuals with the same surname - """ - def __init__(self, report, title, surname, ppl_handle_list): - """ - @param: report -- The instance of the main report class for - this report - @param: title -- Is the title of the web page - @param: surname -- The surname to use - @param: ppl_handle_list -- The list of people for whom we need to create - a page. - """ - BasePage.__init__(self, report, title) - - # module variables - showbirth = report.options['showbirth'] - showdeath = report.options['showdeath'] - showpartner = report.options['showpartner'] - showparents = report.options['showparents'] - - if surname == '': - surname = self._("") - - output_file, sio = self.report.create_file(name_to_md5(surname), "srn") - self.uplink = True - (surnamepage, head, - body) = self.write_header("%s - %s" % (self._("Surname"), surname)) - ldatec = 0 - - # begin SurnameDetail division - with Html("div", class_="content", id="SurnameDetail") as surnamedetail: - body += surnamedetail - - # section title - surnamedetail += Html("h3", html_escape(surname), inline=True) - - # feature request 2356: avoid genitive form - msg = self._("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.") % html_escape(surname) - surnamedetail += Html("p", msg, id="description") - - # begin surname table and thead - with Html("table", class_="infolist primobjlist surname") as table: - surnamedetail += table - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - # Name Column - trow += Html("th", self._("Given Name"), class_="ColumnName", - inline=True) - - if showbirth: - trow += Html("th", self._("Birth"), class_="ColumnDate", - inline=True) - - if showdeath: - trow += Html("th", self._("Death"), class_="ColumnDate", - inline=True) - - if showpartner: - trow += Html("th", self._("Partner"), - class_="ColumnPartner", - inline=True) - - if showparents: - trow += Html("th", self._("Parents"), - class_="ColumnParents", - inline=True) - - # begin table body - tbody = Html("tbody") - table += tbody - - for person_handle in sorted(ppl_handle_list, - key=self.sort_on_name_and_grampsid): - - person = self.r_db.get_person_from_handle(person_handle) - if person.get_change_time() > ldatec: - ldatec = person.get_change_time() - trow = Html("tr") - tbody += trow - - # firstname column - link = self.new_person_link(person_handle, uplink=True, - person=person, - name_style=_NAME_STYLE_FIRST) - trow += Html("td", link, class_="ColumnName") - - # birth column - if showbirth: - tcell = Html("td", class_="ColumnBirth", inline=True) - trow += tcell - - birth_date = _find_birth_date(self.r_db, person) - if birth_date is not None: - if birth_date.fallback: - tcell += Html('em', - self.rlocale.get_date(birth_date), - inline=True) - else: - tcell += self.rlocale.get_date(birth_date) - else: - tcell += " " - - # death column - if showdeath: - tcell = Html("td", class_="ColumnDeath", inline=True) - trow += tcell - - death_date = _find_death_date(self.r_db, person) - if death_date is not None: - if death_date.fallback: - tcell += Html('em', - self.rlocale.get_date(death_date), - inline=True) - else: - tcell += self.rlocale.get_date(death_date) - else: - tcell += " " - - # partner column - if showpartner: - tcell = Html("td", class_="ColumnPartner") - trow += tcell - family_list = person.get_family_handle_list() - first_family = True - if family_list: - fam_count = 0 - for family_handle in family_list: - fam_count += 1 - family = self.r_db.get_family_from_handle( - family_handle) - partner_handle = utils.find_spouse( - person, family) - if partner_handle: - link = self.new_person_link(partner_handle, - uplink=True) - if fam_count < len(family_list): - if isinstance(link, Html): - link.inside += "," - else: - link += ',' - tcell += link - first_family = False - else: - tcell += " " - - # parents column - if showparents: - parent_hdl_list = person.get_parent_family_handle_list() - if parent_hdl_list: - parent_hdl = parent_hdl_list[0] - fam = self.r_db.get_family_from_handle(parent_hdl) - f_id = fam.get_father_handle() - m_id = fam.get_mother_handle() - mother = father = None - if f_id: - father = self.r_db.get_person_from_handle(f_id) - if father: - father_name = self.get_name(father) - if m_id: - mother = self.r_db.get_person_from_handle(m_id) - if mother: - mother_name = self.get_name(mother) - if mother and father: - tcell = Html("span", father_name, - class_="father fatherNmother") - tcell += Html("span", mother_name, - class_="mother") - elif mother: - tcell = Html("span", mother_name, - class_="mother", inline=True) - elif father: - tcell = Html("span", father_name, - class_="father", inline=True) - samerow = False - else: - tcell = " " - samerow = True - trow += Html("td", tcell, - class_="ColumnParents", inline=samerow) - - # add clearline for proper styling - # add footer section - footer = self.write_footer(ldatec) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(surnamepage, output_file, sio, ldatec) - -################################################# -# -# creates the Family List Page and Family Pages -# -################################################# -class FamilyPages(BasePage): - """ - This class is responsible for displaying information about the 'Family' - database objects. It displays this information under the 'Families' - tab. It is told by the 'add_instances' call which 'Family's to display, - and remembers the list of Family. A single call to 'display_pages' - displays both the Family List (Index) page and all the Family - pages. - - The base class 'BasePage' is initialised once for each page that is - displayed. - """ - def __init__(self, report): - """ - @param: report -- The instance of the main report class for - this report - """ - BasePage.__init__(self, report, title="") - self.family_dict = defaultdict(set) - self.person = None - self.familymappages = None - - def display_pages(self, title): - """ - Generate and output the pages under the Family tab, namely the family - index and the individual family pages. - - @param: title -- Is the title of the web page - """ - LOG.debug("obj_dict[Family]") - for item in self.report.obj_dict[Family].items(): - LOG.debug(" %s", str(item)) - - with self.r_user.progress(_("Narrated Web Site Report"), - _("Creating family pages..."), - len(self.report.obj_dict[Family]) + 1 - ) as step: - self.familylistpage(self.report, title, - self.report.obj_dict[Family].keys()) - - for family_handle in self.report.obj_dict[Family]: - step() - self.familypage(self.report, title, family_handle) - - def familylistpage(self, report, title, fam_list): - """ - Create a family index - - @param: report -- The instance of the main report class for - this report - @param: title -- Is the title of the web page - @param: fam_list -- The handle for the place to add - """ - BasePage.__init__(self, report, title) - - output_file, sio = self.report.create_file("families") - familieslistpage, head, body = self.write_header(self._("Families")) - ldatec = 0 - prev_letter = " " - - # begin Family Division - with Html("div", class_="content", id="Relationships") as relationlist: - body += relationlist - - # Families list page message - msg = self._("This page contains an index of all the " - "families/ relationships in the " - "database, sorted by their family name/ surname. " - "Clicking on a person’s " - "name will take you to their " - "family/ relationship’s page.") - relationlist += Html("p", msg, id="description") - - # go through all the families, and construct a dictionary of all the - # people and the families thay are involved in. Note that the people - # in the list may be involved in OTHER families, that are not listed - # because they are not in the original family list. - pers_fam_dict = defaultdict(list) - for family_handle in fam_list: - family = self.r_db.get_family_from_handle(family_handle) - if family: - if family.get_change_time() > ldatec: - ldatec = family.get_change_time() - husband_handle = family.get_father_handle() - spouse_handle = family.get_mother_handle() - if husband_handle: - pers_fam_dict[husband_handle].append(family) - if spouse_handle: - pers_fam_dict[spouse_handle].append(family) - - # add alphabet navigation - index_list = get_first_letters(self.r_db, pers_fam_dict.keys(), - _KEYPERSON, rlocale=self.rlocale) - alpha_nav = alphabet_navigation(index_list, self.rlocale) - if alpha_nav: - relationlist += alpha_nav - - # begin families table and table head - with Html("table", class_="infolist relationships") as table: - relationlist += table - - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - # set up page columns - trow.extend( - Html("th", trans, class_=colclass, inline=True) - for trans, colclass in [ - (self._("Letter"), "ColumnRowLabel"), - (self._("Person"), "ColumnPartner"), - (self._("Family"), "ColumnPartner"), - (self._("Marriage"), "ColumnDate"), - (self._("Divorce"), "ColumnDate") - ] - ) - - tbody = Html("tbody") - table += tbody - - # begin displaying index list - ppl_handle_list = sort_people(self.r_db, pers_fam_dict.keys(), - self.rlocale) - first = True - for (surname, handle_list) in ppl_handle_list: - - if surname and not surname.isspace(): - letter = get_index_letter(first_letter(surname), - index_list, - self.rlocale) - else: - letter = ' ' - - # get person from sorted database list - for person_handle in sorted( - handle_list, key=self.sort_on_name_and_grampsid): - person = self.r_db.get_person_from_handle(person_handle) - if person: - family_list = person.get_family_handle_list() - first_family = True - for family_handle in family_list: - get_family = self.r_db.get_family_from_handle - family = get_family(family_handle) - trow = Html("tr") - tbody += trow - - tcell = Html("td", class_="ColumnRowLabel") - trow += tcell - - if first or primary_difference(letter, - prev_letter, - self.rlocale): - first = False - prev_letter = letter - trow.attr = 'class="BeginLetter"' - ttle = self._("Families beginning with " - "letter ") - tcell += Html("a", letter, name=letter, - title=ttle + letter, - inline=True) - else: - tcell += ' ' - - tcell = Html("td", class_="ColumnPartner") - trow += tcell - - if first_family: - trow.attr = 'class ="BeginFamily"' - - tcell += self.new_person_link( - person_handle, uplink=self.uplink) - - first_family = False - else: - tcell += ' ' - - tcell = Html("td", class_="ColumnPartner") - trow += tcell - - tcell += self.family_link( - family.get_handle(), - self.report.get_family_name(family), - family.get_gramps_id(), self.uplink) - - # family events; such as marriage and divorce - # events - fam_evt_ref_list = family.get_event_ref_list() - tcell1 = Html("td", class_="ColumnDate", - inline=True) - tcell2 = Html("td", class_="ColumnDate", - inline=True) - trow += (tcell1, tcell2) - - if fam_evt_ref_list: - fam_evt_srt_ref_list = sorted( - fam_evt_ref_list, - key=self.sort_on_grampsid) - for evt_ref in fam_evt_srt_ref_list: - evt = self.r_db.get_event_from_handle( - evt_ref.ref) - if evt: - evt_type = evt.get_type() - if evt_type in [EventType.MARRIAGE, - EventType.DIVORCE]: - - if (evt_type == - EventType.MARRIAGE): - tcell1 += self.rlocale.get_date( - evt.get_date_object()) - else: - tcell1 += ' ' - - if (evt_type == - EventType.DIVORCE): - tcell2 += self.rlocale.get_date( - evt.get_date_object()) - else: - tcell2 += ' ' - else: - tcell1 += ' ' - tcell2 += ' ' - first_family = False - - # add clearline for proper styling - # add footer section - footer = self.write_footer(ldatec) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(familieslistpage, output_file, sio, ldatec) - - def familypage(self, report, title, family_handle): - """ - Create a family page - - @param: report -- The instance of the main report class for - this report - @param: title -- Is the title of the web page - @param: family_handle -- The handle for the family to add - """ - family = report.database.get_family_from_handle(family_handle) - if not family: - return - BasePage.__init__(self, report, title, family.get_gramps_id()) - ldatec = family.get_change_time() - - self.bibli = Bibliography() - self.uplink = True - family_name = self.report.get_family_name(family) - self.page_title = family_name - - self.familymappages = report.options["familymappages"] - - output_file, sio = self.report.create_file(family.get_handle(), "fam") - familydetailpage, head, body = self.write_header(family_name) - - # begin FamilyDetaill division - with Html("div", class_="content", - id="RelationshipDetail") as relationshipdetail: - body += relationshipdetail - - # family media list for initial thumbnail - if self.create_media: - media_list = family.get_media_list() - # If Event pages are not being created, then we need to display - # the family event media here - if not self.inc_events: - for evt_ref in family.get_event_ref_list(): - event = self.r_db.get_event_from_handle(evt_ref.ref) - media_list += event.get_media_list() - thumbnail = self.disp_first_img_as_thumbnail(media_list, - family) - if thumbnail: - relationshipdetail += thumbnail - - self.person = None # no longer used - - relationshipdetail += Html( - "h2", self.page_title, inline=True) + ( - Html('sup') + (Html('small') + - self.get_citation_links( - family.get_citation_list()))) - - # display relationships - families = self.display_family_relationships(family, None) - if families is not None: - relationshipdetail += families - - # display additional images as gallery - if self.create_media and media_list: - addgallery = self.disp_add_img_as_gallery(media_list, family) - if addgallery: - relationshipdetail += addgallery - - # Narrative subsection - notelist = family.get_note_list() - if notelist: - relationshipdetail += self.display_note_list(notelist) - - # display family LDS ordinance... - family_lds_ordinance_list = family.get_lds_ord_list() - if family_lds_ordinance_list: - relationshipdetail += self.display_lds_ordinance(family) - - # get attribute list - attrlist = family.get_attribute_list() - if attrlist: - attrsection, attrtable = self.display_attribute_header() - self.display_attr_list(attrlist, attrtable) - relationshipdetail += attrsection - - # source references - srcrefs = self.display_ind_sources(family) - if srcrefs: - relationshipdetail += srcrefs - - # add clearline for proper styling - # add footer section - footer = self.write_footer(ldatec) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(familydetailpage, output_file, sio, ldatec) - -###################################################### -# # -# Place Pages # -# # -###################################################### -class PlacePages(BasePage): - """ - This class is responsible for displaying information about the 'Person' - database objects. It displays this information under the 'Events' - tab. It is told by the 'add_instances' call which 'Person's to display, - and remembers the list of persons. A single call to 'display_pages' - displays both the Event List (Index) page and all the Event - pages. - - The base class 'BasePage' is initialised once for each page that is - displayed. - """ - def __init__(self, report): - """ - @param: report -- The instance of the main report class for - this report - """ - BasePage.__init__(self, report, title="") - self.place_dict = defaultdict(set) - self.placemappages = None - self.mapservice = None - self.person = None - self.familymappages = None - - def display_pages(self, title): - """ - Generate and output the pages under the Place tab, namely the place - index and the individual place pages. - - @param: title -- Is the title of the web page - """ - LOG.debug("obj_dict[Place]") - for item in self.report.obj_dict[Place].items(): - LOG.debug(" %s", str(item)) - with self.r_user.progress(_("Narrated Web Site Report"), - _("Creating place pages"), - len(self.report.obj_dict[Place]) + 1 - ) as step: - - self.placelistpage(self.report, title, - self.report.obj_dict[Place].keys()) - - for place_handle in self.report.obj_dict[Place]: - step() - self.placepage(self.report, title, place_handle) - - def placelistpage(self, report, title, place_handles): - """ - Create a place index - - @param: report -- The instance of the main report class for - this report - @param: title -- Is the title of the web page - @param: place_handles -- The handle for the place to add - """ - BasePage.__init__(self, report, title) - - output_file, sio = self.report.create_file("places") - placelistpage, head, body = self.write_header(self._("Places")) - ldatec = 0 - prev_letter = " " - - # begin places division - with Html("div", class_="content", id="Places") as placelist: - body += placelist - - # place list page message - msg = self._("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.") - placelist += Html("p", msg, id="description") - - # begin alphabet navigation - index_list = get_first_letters(self.r_db, place_handles, - _KEYPLACE, rlocale=self.rlocale) - alpha_nav = alphabet_navigation(index_list, self.rlocale) - if alpha_nav is not None: - placelist += alpha_nav - - # begin places table and table head - with Html("table", - class_="infolist primobjlist placelist") as table: - placelist += table - - # begin table head - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - trow.extend( - Html("th", label, class_=colclass, inline=True) - for (label, colclass) in [ - [self._("Letter"), "ColumnLetter"], - [self._("Place Name | Name"), "ColumnName"], - [self._("State/ Province"), "ColumnState"], - [self._("Country"), "ColumnCountry"], - [self._("Latitude"), "ColumnLatitude"], - [self._("Longitude"), "ColumnLongitude"] - ] - ) - - # bug 9495 : incomplete display of place hierarchy labels - def sort_by_place_name(obj): - """ sort by lower case place name. """ - name = self.report.obj_dict[Place][obj][1] - return name.lower() - - handle_list = sorted(place_handles, - key=lambda x: sort_by_place_name(x)) - first = True - - # begin table body - tbody = Html("tbody") - table += tbody - - for place_handle in handle_list: - place = self.r_db.get_place_from_handle(place_handle) - if place: - if place.get_change_time() > ldatec: - ldatec = place.get_change_time() - place_title = self.report.obj_dict[Place][place_handle][1] - main_location = get_main_location(self.r_db, place) - - if place_title and place_title != " ": - letter = get_index_letter(first_letter(place_title), - index_list, - self.rlocale) - else: - letter = ' ' - - trow = Html("tr") - tbody += trow - - tcell = Html("td", class_="ColumnLetter", inline=True) - trow += tcell - if first or primary_difference(letter, prev_letter, - self.rlocale): - first = False - prev_letter = letter - trow.attr = 'class = "BeginLetter"' - - ttle = self._("Places beginning with letter %s") % letter - tcell += Html("a", letter, name=letter, title=ttle) - else: - tcell += " " - - trow += Html("td", - self.place_link( - place.get_handle(), - place_title, place.get_gramps_id()), - class_="ColumnName") - - trow.extend( - Html("td", data or " ", class_=colclass, - inline=True) - for (colclass, data) in [ - ["ColumnState", - main_location.get(PlaceType.STATE, '')], - ["ColumnCountry", - main_location.get(PlaceType.COUNTRY, '')] - ] - ) - - tcell1 = Html("td", class_="ColumnLatitude", - inline=True) - tcell2 = Html("td", class_="ColumnLongitude", - inline=True) - trow += (tcell1, tcell2) - - if place.lat and place.long: - latitude, longitude = conv_lat_lon(place.lat, - place.long, - "DEG") - tcell1 += latitude - tcell2 += longitude - else: - tcell1 += ' ' - tcell2 += ' ' - - # add clearline for proper styling - # add footer section - footer = self.write_footer(ldatec) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(placelistpage, output_file, sio, ldatec) - - def placepage(self, report, title, place_handle): - """ - Create a place page - - @param: report -- The instance of the main report class for - this report - @param: title -- Is the title of the web page - @param: place_handle -- The handle for the place to add - """ - place = report.database.get_place_from_handle(place_handle) - if not place: - return None - BasePage.__init__(self, report, title, place.get_gramps_id()) - self.bibli = Bibliography() - place_name = self.report.obj_dict[Place][place_handle][1] - ldatec = place.get_change_time() - - output_file, sio = self.report.create_file(place_handle, "plc") - self.uplink = True - self.page_title = place_name - placepage, head, body = self.write_header(_("Places")) - - self.placemappages = self.report.options['placemappages'] - self.mapservice = self.report.options['mapservice'] - self.googlemapkey = self.report.options['googlemapkey'] - - # begin PlaceDetail Division - with Html("div", class_="content", id="PlaceDetail") as placedetail: - body += placedetail - - if self.create_media: - media_list = place.get_media_list() - thumbnail = self.disp_first_img_as_thumbnail(media_list, - place) - if thumbnail is not None: - placedetail += thumbnail - - # add section title - placedetail += Html("h3", - html_escape(place_name), - inline=True) - - # begin summaryarea division and places table - with Html("div", id='summaryarea') as summaryarea: - placedetail += summaryarea - - with Html("table", class_="infolist place") as table: - summaryarea += table - - # list the place fields - self.dump_place(place, table) - - # place gallery - if self.create_media: - placegallery = self.disp_add_img_as_gallery(media_list, place) - if placegallery is not None: - placedetail += placegallery - - # place notes - notelist = self.display_note_list(place.get_note_list()) - if notelist is not None: - placedetail += notelist - - # place urls - urllinks = self.display_url_list(place.get_url_list()) - if urllinks is not None: - placedetail += urllinks - - # add place map here - # Link to Gramps marker - fname = "/".join(['images', 'marker.png']) - marker_path = self.report.build_url_image("marker.png", - "images", self.uplink) - - if self.placemappages: - if place and (place.lat and place.long): - latitude, longitude = conv_lat_lon(place.get_latitude(), - place.get_longitude(), - "D.D8") - if not longitude: - longitude = 0.0 - if not latitude: - latitude = 0.0 - placetitle = place_name - - # add narrative-maps CSS... - fname = "/".join(["css", "narrative-maps.css"]) - url = self.report.build_url_fname(fname, None, self.uplink) - head += Html("link", href=url, type="text/css", - media="screen", rel="stylesheet") - - # add MapService specific javascript code - src_js = GOOGLE_MAPS + "api/js?sensor=false" - if self.mapservice == "Google": - if self.googlemapkey: - src_js += "&key=" + self.googlemapkey - head += Html("script", type="text/javascript", - src=src_js, inline=True) - else: - url = self.secure_mode - url += "maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" - head += Html("link", href=url, type="text/javascript", - rel="stylesheet") - src_js = self.secure_mode - src_js += "ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" - head += Html("script", type="text/javascript", - src=src_js, inline=True) - src_js = self.secure_mode - src_js += "openlayers.org/en/v3.17.1/build/ol.js" - head += Html("script", type="text/javascript", - src=src_js, inline=True) - url = self.secure_mode - url += "openlayers.org/en/v3.17.1/css/ol.css" - head += Html("link", href=url, type="text/javascript", - rel="stylesheet") - src_js = self.secure_mode - src_js += "maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" - head += Html("script", type="text/javascript", - src=src_js, inline=True) - - # section title - placedetail += Html("h4", self._("Place Map"), inline=True) - - # begin map_canvas division - with Html("div", id="map_canvas", inline=True) as canvas: - placedetail += canvas - - # Begin inline javascript code because jsc is a - # docstring, it does NOT have to be properly indented - if self.mapservice == "Google": - with Html("script", type="text/javascript", - indent=False) as jsc: - head += jsc - - # Google adds Latitude/ Longitude to its maps... - plce = placetitle.replace("'", "\\'") - jsc += MARKER_PATH % marker_path - jsc += MARKERS % ([[plce, - latitude, - longitude, - 1]], - latitude, longitude, - 10) - - else: - # OpenStreetMap (OSM) adds Longitude/ Latitude - # to its maps, and needs a country code in - # lowercase letters... - with Html("script", type="text/javascript") as jsc: - canvas += jsc - param1 = xml_lang()[3:5].lower() - jsc += MARKER_PATH % marker_path - jsc += OSM_MARKERS % ([[longitude, latitude, - placetitle]], - longitude, latitude, 10) - - # add javascript function call to body element - body.attr += ' onload = "initialize();" ' - - # add div for popups. - with Html("div", id="popup", inline=True) as popup: - placedetail += popup - - # source references - srcrefs = self.display_ind_sources(place) - if srcrefs is not None: - placedetail += srcrefs - - # References list - ref_list = self.display_bkref_list(Place, place_handle) - if ref_list is not None: - placedetail += ref_list - - # add clearline for proper styling - # add footer section - footer = self.write_footer(ldatec) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(placepage, output_file, sio, ldatec) - -################################################# -# -# creates the Event List Page and EventPages -# -################################################# -class EventPages(BasePage): - """ - This class is responsible for displaying information about the 'Person' - database objects. It displays this information under the 'Events' - tab. It is told by the 'add_instances' call which 'Person's to display, - and remembers the list of persons. A single call to 'display_pages' - displays both the Event List (Index) page and all the Event - pages. - - The base class 'BasePage' is initialised once for each page that is - displayed. - """ - def __init__(self, report): - """ - @param: report -- The instance of the main report class for - this report - """ - BasePage.__init__(self, report, title="") - self.event_handle_list = [] - self.event_types = [] - self.event_dict = defaultdict(set) - - def display_pages(self, title): - """ - Generate and output the pages under the Event tab, namely the event - index and the individual event pages. - - @param: title -- Is the title of the web page - """ - LOG.debug("obj_dict[Event]") - for item in self.report.obj_dict[Event].items(): - LOG.debug(" %s", str(item)) - event_handle_list = self.report.obj_dict[Event].keys() - event_types = [] - for event_handle in event_handle_list: - event = self.r_db.get_event_from_handle(event_handle) - event_types.append(self._(event.get_type().xml_str())) - with self.r_user.progress(_("Narrated Web Site Report"), - _("Creating event pages"), - len(event_handle_list) + 1 - ) as step: - self.eventlistpage(self.report, title, event_types, - event_handle_list) - - for event_handle in event_handle_list: - step() - self.eventpage(self.report, title, event_handle) - - - def eventlistpage(self, report, title, event_types, event_handle_list): - """ - Will create the event list page - - @param: report -- The instance of the main report class for - this report - @param: title -- Is the title of the web page - @param: event_types -- A list of the type in the events database - @param: event_handle_list -- A list of event handles - """ - BasePage.__init__(self, report, title) - ldatec = 0 - prev_letter = " " - - output_file, sio = self.report.create_file("events") - eventslistpage, head, body = self.write_header(self._("Events")) - - # begin events list division - with Html("div", class_="content", id="EventList") as eventlist: - body += eventlist - - msg = self._("This page contains an index of all the events in the " - "database, sorted by their type and date (if one is " - "present). Clicking on an event’s Gramps ID " - "will open a page for that event.") - eventlist += Html("p", msg, id="description") - - # get alphabet navigation... - index_list = get_first_letters(self.r_db, event_types, - _ALPHAEVENT) - alpha_nav = alphabet_navigation(index_list, self.rlocale) - if alpha_nav: - eventlist += alpha_nav - - # begin alphabet event table - with Html("table", - class_="infolist primobjlist alphaevent") as table: - eventlist += table - - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - trow.extend( - Html("th", label, class_=colclass, inline=True) - for (label, colclass) in [ - (self._("Letter"), "ColumnRowLabel"), - (self._("Type"), "ColumnType"), - (self._("Date"), "ColumnDate"), - (self._("Gramps ID"), "ColumnGRAMPSID"), - (self._("Person"), "ColumnPerson") - ] - ) - - tbody = Html("tbody") - table += tbody - - # separate events by their type and then thier event handles - for (evt_type, - data_list) in sort_event_types(self.r_db, - event_types, - event_handle_list, - self.rlocale): - first = True - _event_displayed = [] - - # sort datalist by date of event and by event handle... - data_list = sorted(data_list, key=itemgetter(0, 1)) - first_event = True - - for (sort_value, event_handle) in data_list: - event = self.r_db.get_event_from_handle(event_handle) - _type = event.get_type() - gid = event.get_gramps_id() - if event.get_change_time() > ldatec: - ldatec = event.get_change_time() - - # check to see if we have listed this gramps_id yet? - if gid not in _event_displayed: - - # family event - if int(_type) in _EVENTMAP: - handle_list = set( - self.r_db.find_backlink_handles( - event_handle, - include_classes=['Family', 'Person'])) - else: - handle_list = set( - self.r_db.find_backlink_handles( - event_handle, - include_classes=['Person'])) - if handle_list: - - trow = Html("tr") - tbody += trow - - # set up hyperlinked letter for - # alphabet_navigation - tcell = Html("td", class_="ColumnLetter", - inline=True) - trow += tcell - - if evt_type and not evt_type.isspace(): - letter = get_index_letter( - self._(str(evt_type)[0].capitalize()), - index_list, self.rlocale) - else: - letter = " " - - if first or primary_difference(letter, - prev_letter, - self.rlocale): - first = False - prev_letter = letter - t_a = 'class = "BeginLetter BeginType"' - trow.attr = t_a - ttle = self._("Event types beginning " - "with letter %s") % letter - tcell += Html("a", letter, name=letter, - id_=letter, title=ttle, - inline=True) - else: - tcell += " " - - # display Event type if first in the list - tcell = Html("td", class_="ColumnType", - title=self._(evt_type), - inline=True) - trow += tcell - if first_event: - tcell += self._(evt_type) - if trow.attr == "": - trow.attr = 'class = "BeginType"' - else: - tcell += " " - - # event date - tcell = Html("td", class_="ColumnDate", - inline=True) - trow += tcell - date = Date.EMPTY - if event: - date = event.get_date_object() - if date and date is not Date.EMPTY: - tcell += self.rlocale.get_date(date) - else: - tcell += " " - - # Gramps ID - trow += Html("td", class_="ColumnGRAMPSID") + ( - self.event_grampsid_link(event_handle, - gid, None) - ) - - # Person(s) column - tcell = Html("td", class_="ColumnPerson") - trow += tcell - - # classname can either be a person or a family - first_person = True - - # get person(s) for ColumnPerson - sorted_list = sorted(handle_list) - self.complete_people(tcell, first_person, - sorted_list, - uplink=False) - - _event_displayed.append(gid) - first_event = False - - # add clearline for proper styling - # add footer section - footer = self.write_footer(ldatec) - body += (FULLCLEAR, footer) - - # send page ut for processing - # and close the file - self.xhtml_writer(eventslistpage, output_file, sio, ldatec) - - def _geteventdate(self, event_handle): - """ - Get the event date - - @param: event_handle -- The handle for the event to use - """ - event_date = Date.EMPTY - event = self.r_db.get_event_from_handle(event_handle) - if event: - date = event.get_date_object() - if date: - - # returns the date in YYYY-MM-DD format - return Date(date.get_year_calendar("Gregorian"), - date.get_month(), date.get_day()) - - # return empty date string - return event_date - - def event_grampsid_link(self, handle, grampsid, uplink): - """ - Create a hyperlink from event handle, but show grampsid - - @param: handle -- The handle for the event - @param: grampsid -- The gramps ID to display - @param: uplink -- If True, then "../../../" is inserted in front of - the result. - """ - url = self.report.build_url_fname_html(handle, "evt", uplink) - - # return hyperlink to its caller - return Html("a", grampsid, href=url, title=grampsid, inline=True) - - def eventpage(self, report, title, event_handle): - """ - Creates the individual event page - - @param: report -- The instance of the main report class for - this report - @param: title -- Is the title of the web page - @param: event_handle -- The event handle for the database - """ - event = report.database.get_event_from_handle(event_handle) - BasePage.__init__(self, report, title, event.get_gramps_id()) - if not event: - return None - - ldatec = event.get_change_time() - event_media_list = event.get_media_list() - - self.uplink = True - subdirs = True - evt_type = self._(event.get_type().xml_str()) - self.page_title = "%(eventtype)s" % {'eventtype' : evt_type} - self.bibli = Bibliography() - - output_file, sio = self.report.create_file(event_handle, "evt") - eventpage, head, body = self.write_header(self._("Events")) - - # start event detail division - with Html("div", class_="content", id="EventDetail") as eventdetail: - body += eventdetail - - thumbnail = self.disp_first_img_as_thumbnail(event_media_list, - event) - if thumbnail is not None: - eventdetail += thumbnail - - # display page title - eventdetail += Html("h3", self.page_title, inline=True) - - # begin eventdetail table - with Html("table", class_="infolist eventlist") as table: - eventdetail += table - - tbody = Html("tbody") - table += tbody - - evt_gid = event.get_gramps_id() - if not self.noid and evt_gid: - trow = Html("tr") + ( - Html("td", self._("Gramps ID"), - class_="ColumnAttribute", inline=True), - Html("td", evt_gid, - class_="ColumnGRAMPSID", inline=True) - ) - tbody += trow - - # get event data - # - # for more information: see get_event_data() - # - event_data = self.get_event_data(event, event_handle, - subdirs, evt_gid) - - for (label, colclass, data) in event_data: - if data: - trow = Html("tr") + ( - Html("td", label, class_="ColumnAttribute", - inline=True), - Html('td', data, class_="Column" + colclass) - ) - tbody += trow - - # Narrative subsection - notelist = event.get_note_list() - notelist = self.display_note_list(notelist) - if notelist is not None: - eventdetail += notelist - - # get attribute list - attrlist = event.get_attribute_list() - if attrlist: - attrsection, attrtable = self.display_attribute_header() - self.display_attr_list(attrlist, attrtable) - eventdetail += attrsection - - # event source references - srcrefs = self.display_ind_sources(event) - if srcrefs is not None: - eventdetail += srcrefs - - # display additional images as gallery - if self.create_media: - addgallery = self.disp_add_img_as_gallery(event_media_list, - event) - if addgallery: - eventdetail += addgallery - - # References list - ref_list = self.display_bkref_list(Event, event_handle) - if ref_list is not None: - eventdetail += ref_list - - # add clearline for proper styling - # add footer section - footer = self.write_footer(ldatec) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the page - self.xhtml_writer(eventpage, output_file, sio, ldatec) - -################################################# -# -# Creates the Surname List page -# -################################################# -class SurnameListPage(BasePage): - """ - This class is responsible for displaying the list of Surnames - """ - ORDER_BY_NAME = 0 - ORDER_BY_COUNT = 1 - - def __init__(self, report, title, ppl_handle_list, - order_by=ORDER_BY_NAME, filename="surnames"): - """ - @param: report -- The instance of the main report class for - this report - @param: title -- Is the title of the web page - @param: ppl_handle_list -- The list of people for whom we need to create - a page. - @param: order_by -- The way to sort surnames : - Surnames or Surnames count - @param: filename -- The name to use for the Surnames page - """ - BasePage.__init__(self, report, title) - prev_surname = "" - prev_letter = " " - - if order_by == self.ORDER_BY_NAME: - output_file, sio = self.report.create_file(filename) - surnamelistpage, head, body = self.write_header(self._('Surnames')) - else: - output_file, sio = self.report.create_file("surnames_count") - (surnamelistpage, head, - body) = self.write_header(self._('Surnames by person count')) - - # begin surnames division - with Html("div", class_="content", id="surnames") as surnamelist: - body += surnamelist - - # page message - msg = self._('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.') - surnamelist += Html("p", msg, id="description") - - # add alphabet navigation... - # only if surname list not surname count - if order_by == self.ORDER_BY_NAME: - index_list = get_first_letters(self.r_db, ppl_handle_list, - _KEYPERSON, rlocale=self.rlocale) - alpha_nav = alphabet_navigation(index_list, self.rlocale) - if alpha_nav is not None: - surnamelist += alpha_nav - - if order_by == self.ORDER_BY_COUNT: - table_id = 'SortByCount' - else: - table_id = 'SortByName' - - # begin surnamelist table and table head - with Html("table", class_="infolist primobjlist surnamelist", - id=table_id) as table: - surnamelist += table - - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - trow += Html("th", self._("Letter"), class_="ColumnLetter", - inline=True) - - # create table header surname hyperlink - fname = self.report.surname_fname + self.ext - tcell = Html("th", class_="ColumnSurname", inline=True) - trow += tcell - hyper = Html("a", self._("Surname"), - href=fname, title=self._("Surnames")) - tcell += hyper - - # create table header number of people hyperlink - fname = "surnames_count" + self.ext - tcell = Html("th", class_="ColumnQuantity", inline=True) - trow += tcell - num_people = self._("Number of People") - hyper = Html("a", num_people, href=fname, title=num_people) - tcell += hyper - - # begin table body - with Html("tbody") as tbody: - table += tbody - - ppl_handle_list = sort_people(self.r_db, ppl_handle_list, - self.rlocale) - if order_by == self.ORDER_BY_COUNT: - temp_list = {} - for (surname, data_list) in ppl_handle_list: - index_val = "%90d_%s" % (999999999-len(data_list), - surname) - temp_list[index_val] = (surname, data_list) - - ppl_handle_list = (temp_list[key] - for key in sorted(temp_list, - key=self.rlocale.sort_key)) - - first = True - first_surname = True - - for (surname, data_list) in ppl_handle_list: - - if surname and not surname.isspace(): - letter = first_letter(surname) - if order_by == self.ORDER_BY_NAME: - # There will only be an alphabetic index list if - # the ORDER_BY_NAME page is being generated - letter = get_index_letter(letter, index_list, - self.rlocale) - else: - letter = ' ' - surname = self._("") - - trow = Html("tr") - tbody += trow - - tcell = Html("td", class_="ColumnLetter", inline=True) - trow += tcell - - if first or primary_difference(letter, prev_letter, - self.rlocale): - first = False - prev_letter = letter - trow.attr = 'class = "BeginLetter"' - ttle = self._("Surnames beginning with " - "letter %s") % letter - hyper = Html("a", letter, name=letter, - title=ttle, inline=True) - tcell += hyper - elif first_surname or surname != prev_surname: - first_surname = False - tcell += " " - prev_surname = surname - - trow += Html("td", - self.surname_link(name_to_md5(surname), - #html_escape(surname)), - surname), - class_="ColumnSurname", inline=True) - - trow += Html("td", len(data_list), - class_="ColumnQuantity", inline=True) - - # create footer section - # add clearline for proper styling - footer = self.write_footer(None) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(surnamelistpage, - output_file, sio, 0) # 0 => current date modification - - def surname_link(self, fname, name, opt_val=None, uplink=False): - """ - Create a link to the surname page. - - @param: fname -- Path to the file name - @param: name -- Name to see in the link - @param: opt_val -- Option value to use - @param: uplink -- If True, then "../../../" is inserted in front of - the result. - """ - url = self.report.build_url_fname_html(fname, "srn", uplink) - hyper = Html("a", html_escape(name), href=url, title=name, inline=True) - if opt_val is not None: - hyper += opt_val - - # return hyperlink to its caller - return hyper - -class IntroductionPage(BasePage): - """ - This class is responsible for displaying information - about the introduction page. - """ - def __init__(self, report, title): - """ - @param: report -- The instance of the main report class for - this report - @param: title -- Is the title of the web page - """ - BasePage.__init__(self, report, title) - ldatec = 0 - - output_file, sio = self.report.create_file(report.intro_fname) - intropage, head, body = self.write_header(self._('Introduction')) - - # begin Introduction division - with Html("div", class_="content", id="Introduction") as section: - body += section - - introimg = self.add_image('introimg') - if introimg is not None: - section += introimg - - note_id = report.options['intronote'] - if note_id: - note = self.r_db.get_note_from_gramps_id(note_id) - note_text = self.get_note_format(note, False) - - # attach note - section += note_text - - # last modification of this note - ldatec = note.get_change_time() - - # add clearline for proper styling - # create footer section - footer = self.write_footer(ldatec) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(intropage, output_file, sio, ldatec) - -class HomePage(BasePage): - """ - This class is responsible for displaying information about the Home page. - """ - def __init__(self, report, title): - """ - @param: report -- The instance of the main report class for - this report - @param: title -- Is the title of the web page - """ - BasePage.__init__(self, report, title) - ldatec = 0 - - output_file, sio = self.report.create_file("index") - homepage, head, body = self.write_header(self._('Home')) - - # begin home division - with Html("div", class_="content", id="Home") as section: - body += section - - homeimg = self.add_image('homeimg') - if homeimg is not None: - section += homeimg - - note_id = report.options['homenote'] - if note_id: - note = self.r_db.get_note_from_gramps_id(note_id) - note_text = self.get_note_format(note, False) - - # attach note - section += note_text - - # last modification of this note - ldatec = note.get_change_time() - - # create clear line for proper styling - # create footer section - footer = self.write_footer(ldatec) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(homepage, output_file, sio, ldatec) - -################################################# -# -# Passes citations through to the Sources page -# -################################################# -class CitationPages(BasePage): - """ - This class is responsible for displaying information about the 'Citation' - database objects. It passes this information to the 'Sources' tab. It is - told by the 'add_instances' call which 'Citation's to display. - """ - def __init__(self, report): - """ - @param: report -- The instance of the main report class for - this report - """ - BasePage.__init__(self, report, title="") - - def display_pages(self, title): - pass - -################################################# -# -# creates the Source List Page and Source Pages -# -################################################# -class SourcePages(BasePage): - """ - This class is responsible for displaying information about the 'Source' - database objects. It displays this information under the 'Sources' - tab. It is told by the 'add_instances' call which 'Source's to display, - and remembers the list of persons. A single call to 'display_pages' - displays both the Individual List (Index) page and all the Individual - pages. - - The base class 'BasePage' is initialised once for each page that is - displayed. - """ - def __init__(self, report): - """ - @param: report -- The instance of the main report class for - this report - """ - BasePage.__init__(self, report, title="") - self.source_dict = defaultdict(set) - self.navigation = None - self.citationreferents = None - - def display_pages(self, title): - """ - Generate and output the pages under the Sources tab, namely the sources - index and the individual sources pages. - - @param: title -- Is the title of the web page - """ - LOG.debug("obj_dict[Source]") - for item in self.report.obj_dict[Source].items(): - LOG.debug(" %s", str(item)) - with self.r_user.progress(_("Narrated Web Site Report"), - _("Creating source pages"), - len(self.report.obj_dict[Source]) + 1 - ) as step: - self.sourcelistpage(self.report, title, - self.report.obj_dict[Source].keys()) - - for source_handle in self.report.obj_dict[Source]: - step() - self.sourcepage(self.report, title, source_handle) - - - def sourcelistpage(self, report, title, source_handles): - """ - Generate and output the Sources index page. - - @param: report -- The instance of the main report class for - this report - @param: title -- Is the title of the web page - @param: source_handles -- A list of the handles of the sources to be - displayed - """ - BasePage.__init__(self, report, title) - - source_dict = {} - - output_file, sio = self.report.create_file("sources") - sourcelistpage, head, body = self.write_header(self._("Sources")) - - # begin source list division - with Html("div", class_="content", id="Sources") as sourceslist: - body += sourceslist - - # Sort the sources - for handle in source_handles: - source = self.r_db.get_source_from_handle(handle) - if source is not None: - key = source.get_title() + source.get_author() - key += str(source.get_gramps_id()) - source_dict[key] = (source, handle) - - keys = sorted(source_dict, key=self.rlocale.sort_key) - - msg = self._("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.") - sourceslist += Html("p", msg, id="description") - - # begin sourcelist table and table head - with Html("table", - class_="infolist primobjlist sourcelist") as table: - sourceslist += table - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - header_row = [ - (self._("Number"), "ColumnRowLabel"), - (self._("Author"), "ColumnAuthor"), - (self._("Source Name|Name"), "ColumnName")] - - trow.extend( - Html("th", label or " ", class_=colclass, inline=True) - for (label, colclass) in header_row - ) - - # begin table body - tbody = Html("tbody") - table += tbody - - for index, key in enumerate(keys): - source, source_handle = source_dict[key] - - trow = Html("tr") + ( - Html("td", index + 1, class_="ColumnRowLabel", - inline=True) - ) - tbody += trow - trow.extend( - Html("td", source.get_author(), class_="ColumnAuthor", - inline=True) - ) - trow.extend( - Html("td", self.source_link(source_handle, - source.get_title(), - source.get_gramps_id()), - class_="ColumnName") - ) - - # add clearline for proper styling - # add footer section - footer = self.write_footer(None) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(sourcelistpage, output_file, sio, 0) - - def sourcepage(self, report, title, source_handle): - """ - Generate and output an individual Source page. - - @param: report -- The instance of the main report class - for this report - @param: title -- Is the title of the web page - @param: source_handle -- The handle of the source to be output - """ - source = report.database.get_source_from_handle(source_handle) - BasePage.__init__(self, report, title, source.get_gramps_id()) - if not source: - return - - self.page_title = source.get_title() - - inc_repositories = self.report.options["inc_repository"] - self.navigation = self.report.options['navigation'] - self.citationreferents = self.report.options['citationreferents'] - - output_file, sio = self.report.create_file(source_handle, "src") - self.uplink = True - sourcepage, head, body = self.write_header( - "%s - %s" % (self._('Sources'), self.page_title)) - - ldatec = 0 - # begin source detail division - with Html("div", class_="content", id="SourceDetail") as sourcedetail: - body += sourcedetail - - media_list = source.get_media_list() - if self.create_media and media_list: - thumbnail = self.disp_first_img_as_thumbnail(media_list, - source) - if thumbnail is not None: - sourcedetail += thumbnail - - # add section title - sourcedetail += Html("h3", html_escape(source.get_title()), - inline=True) - - # begin sources table - with Html("table", class_="infolist source") as table: - sourcedetail += table - - tbody = Html("tbody") - table += tbody - - source_gid = False - if not self.noid and self.gid: - source_gid = source.get_gramps_id() - - # last modification of this source - ldatec = source.get_change_time() - - for (label, value) in [ - (self._("Gramps ID"), source_gid), - (self._("Author"), source.get_author()), - (self._("Abbreviation"), source.get_abbreviation()), - (self._("Publication information"), - source.get_publication_info())]: - if value: - trow = Html("tr") + ( - Html("td", label, class_="ColumnAttribute", - inline=True), - Html("td", value, class_="ColumnValue", inline=True) - ) - tbody += trow - - # Source notes - notelist = self.display_note_list(source.get_note_list()) - if notelist is not None: - sourcedetail += notelist - - # additional media from Source (if any?) - if self.create_media and media_list: - sourcemedia = self.disp_add_img_as_gallery(media_list, source) - if sourcemedia is not None: - sourcedetail += sourcemedia - - # Source Data Map... - src_data_map = self.write_srcattr(source.get_attribute_list()) - if src_data_map is not None: - sourcedetail += src_data_map - - # Source Repository list - if inc_repositories: - repo_list = self.dump_repository_ref_list( - source.get_reporef_list()) - if repo_list is not None: - sourcedetail += repo_list - - # Source references list - ref_list = self.display_bkref_list(Source, source_handle) - if ref_list is not None: - sourcedetail += ref_list - - # add clearline for proper styling - # add footer section - footer = self.write_footer(ldatec) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(sourcepage, output_file, sio, ldatec) - -################################################# -# -# creates the Media List Page and Media Pages -# -################################################# -class MediaPages(BasePage): - """ - This class is responsible for displaying information about the 'Media' - database objects. It displays this information under the 'Individuals' - tab. It is told by the 'add_instances' call which 'Media's to display, - and remembers the list of persons. A single call to 'display_pages' - displays both the Individual List (Index) page and all the Individual - pages. - - The base class 'BasePage' is initialised once for each page that is - displayed. - """ - def __init__(self, report): - """ - @param: report -- The instance of the main report class for this report - """ - BasePage.__init__(self, report, title="") - self.media_dict = defaultdict(set) - - def display_pages(self, title): - """ - Generate and output the pages under the Media tab, namely the media - index and the individual media pages. - - @param: title -- Is the title of the web page - """ - LOG.debug("obj_dict[Media]") - for item in self.report.obj_dict[Media].items(): - LOG.debug(" %s", str(item)) - with self.r_user.progress(_("Narrated Web Site Report"), - _("Creating media pages"), - len(self.report.obj_dict[Media]) + 1 - ) as step: - # bug 8950 : it seems it's better to sort on desc + gid. - def sort_by_desc_and_gid(obj): - """ - Sort by media description and gramps ID - """ - return (obj.desc.lower(), obj.gramps_id) - - sorted_media_handles = sorted( - self.report.obj_dict[Media].keys(), - key=lambda x: sort_by_desc_and_gid( - self.r_db.get_media_from_handle(x))) - self.medialistpage(self.report, title, sorted_media_handles) - - prev = None - total = len(sorted_media_handles) - index = 1 - for handle in sorted_media_handles: - gc.collect() # Reduce memory usage when there are many images. - next_ = None if index == total else sorted_media_handles[index] - step() - self.mediapage(self.report, title, - handle, (prev, next_, index, total)) - prev = handle - index += 1 - - def medialistpage(self, report, title, sorted_media_handles): - """ - Generate and output the Media index page. - - @param: report -- The instance of the main report class - for this report - @param: title -- Is the title of the web page - @param: sorted_media_handles -- A list of the handles of the media to be - displayed sorted by the media title - """ - BasePage.__init__(self, report, title) - - output_file, sio = self.report.create_file("media") - medialistpage, head, body = self.write_header(self._('Media')) - - ldatec = 0 - # begin gallery division - with Html("div", class_="content", id="Gallery") as medialist: - body += medialist - - msg = self._("This page contains an index of all the media objects " - "in the database, sorted by their title. Clicking on " - "the title will take you to that " - "media object’s page. " - "If you see media size dimensions " - "above an image, click on the " - "image to see the full sized version. ") - medialist += Html("p", msg, id="description") - - # begin gallery table and table head - with Html("table", - class_="infolist primobjlist gallerylist") as table: - medialist += table - - # begin table head - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - trow.extend( - Html("th", trans, class_=colclass, inline=True) - for trans, colclass in [ - (" ", "ColumnRowLabel"), - (self._("Media | Name"), "ColumnName"), - (self._("Date"), "ColumnDate"), - (self._("Mime Type"), "ColumnMime") - ] - ) - - # begin table body - tbody = Html("tbody") - table += tbody - - index = 1 - for media_handle in sorted_media_handles: - media = self.r_db.get_media_from_handle(media_handle) - if media: - if media.get_change_time() > ldatec: - ldatec = media.get_change_time() - title = media.get_description() or "[untitled]" - - trow = Html("tr") - tbody += trow - - media_data_row = [ - [index, "ColumnRowLabel"], - [self.media_ref_link(media_handle, - title), "ColumnName"], - [self.rlocale.get_date(media.get_date_object()), - "ColumnDate"], - [media.get_mime_type(), "ColumnMime"]] - - trow.extend( - Html("td", data, class_=colclass) - for data, colclass in media_data_row - ) - index += 1 - - def sort_by_desc_and_gid(obj): - """ - Sort by media description and gramps ID - """ - return (obj.desc, obj.gramps_id) - - unused_media_handles = [] - if self.create_unused_media: - # add unused media - media_list = self.r_db.get_media_handles() - for media_ref in media_list: - if media_ref not in self.report.obj_dict[Media]: - unused_media_handles.append(media_ref) - unused_media_handles = sorted( - unused_media_handles, - key=lambda x: sort_by_desc_and_gid( - self.r_db.get_media_from_handle(x))) - - idx = 1 - prev = None - total = len(unused_media_handles) - if total > 0: - trow += Html("tr") - trow.extend( - Html("td", Html("h4", " "), inline=True) + - Html("td", - Html("h4", - self._("Below unused media objects"), - inline=True), - class_="") + - Html("td", Html("h4", " "), inline=True) + - Html("td", Html("h4", " "), inline=True) - ) - for media_handle in unused_media_handles: - media = self.r_db.get_media_from_handle(media_handle) - gc.collect() # Reduce memory usage when many images. - next_ = None if idx == total else unused_media_handles[idx] - trow += Html("tr") - media_data_row = [ - [index, "ColumnRowLabel"], - [self.media_ref_link(media_handle, - media.get_description()), - "ColumnName"], - [self.rlocale.get_date(media.get_date_object()), - "ColumnDate"], - [media.get_mime_type(), "ColumnMime"]] - trow.extend( - Html("td", data, class_=colclass) - for data, colclass in media_data_row - ) - self.mediapage(self.report, title, - media_handle, (prev, next_, index, total)) - prev = media_handle - index += 1 - idx += 1 - - # add footer section - # add clearline for proper styling - footer = self.write_footer(ldatec) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(medialistpage, output_file, sio, ldatec) - - def media_ref_link(self, handle, name, uplink=False): - """ - Create a reference link to a media - - @param: handle -- The media handle - @param: name -- The name to use for the link - @param: uplink -- If True, then "../../../" is inserted in front of the - result. - """ - # get media url - url = self.report.build_url_fname_html(handle, "img", uplink) - - # get name - name = html_escape(name) - - # begin hyper link - hyper = Html("a", name, href=url, title=name) - - # return hyperlink to its callers - return hyper - - def mediapage(self, report, title, media_handle, info): - """ - Generate and output an individual Media page. - - @param: report -- The instance of the main report class - for this report - @param: title -- Is the title of the web page - @param: media_handle -- The media handle to use - @param: info -- A tuple containing the media handle for the - next and previous media, the current page - number, and the total number of media pages - """ - media = report.database.get_media_from_handle(media_handle) - BasePage.__init__(self, report, title, media.gramps_id) - (prev, next_, page_number, total_pages) = info - - ldatec = media.get_change_time() - - # get media rectangles - _region_items = self.media_ref_rect_regions(media_handle) - - output_file, sio = self.report.create_file(media_handle, "img") - self.uplink = True - - self.bibli = Bibliography() - - # get media type to be used primarily with "img" tags - mime_type = media.get_mime_type() - #mtype = get_description(mime_type) - - if mime_type: - #note_only = False - newpath = self.copy_source_file(media_handle, media) - target_exists = newpath is not None - else: - #note_only = True - target_exists = False - - self.copy_thumbnail(media_handle, media) - self.page_title = media.get_description() - esc_page_title = html_escape(self.page_title) - (mediapage, head, - body) = self.write_header("%s - %s" % (self._("Media"), - self.page_title)) - - # if there are media rectangle regions, attach behaviour style sheet - if _region_items: - - fname = "/".join(["css", "behaviour.css"]) - url = self.report.build_url_fname(fname, None, self.uplink) - head += Html("link", href=url, type="text/css", - media="screen", rel="stylesheet") - - # begin MediaDetail division - with Html("div", class_="content", id="GalleryDetail") as mediadetail: - body += mediadetail - - # media navigation - with Html("div", id="GalleryNav", role="navigation") as medianav: - mediadetail += medianav - if prev: - medianav += self.media_nav_link(prev, - self._("Previous"), True) - data = self._('%(strong1_start)s%(page_number)d%(strong_end)s ' - 'of %(strong2_start)s%(total_pages)d%(strong_end)s' - ) % {'strong1_start' : - '', - 'strong2_start' : - '', - 'strong_end' : '', - 'page_number' : page_number, - 'total_pages' : total_pages} - medianav += Html("span", data, id="GalleryPages") - if next_: - medianav += self.media_nav_link(next_, self._("Next"), True) - - # missing media error message - errormsg = self._("The file has been moved or deleted.") - - # begin summaryarea division - with Html("div", id="summaryarea") as summaryarea: - mediadetail += summaryarea - if mime_type: - if mime_type.startswith("image"): - if not target_exists: - with Html("div", id="MediaDisplay") as mediadisplay: - summaryarea += mediadisplay - mediadisplay += Html("span", errormsg, - class_="MissingImage") - - else: - # Check how big the image is relative to the - # requested 'initial' image size. - # If it's significantly bigger, scale it down to - # improve the site's responsiveness. We don't want - # the user to have to await a large download - # unnecessarily. Either way, set the display image - # size as requested. - orig_image_path = media_path_full(self.r_db, - media.get_path()) - #mtime = os.stat(orig_image_path).st_mtime - (width, height) = image_size(orig_image_path) - max_width = self.report.options[ - 'maxinitialimagewidth'] - max_height = self.report.options[ - 'maxinitialimageheight'] - if width != 0 and height != 0: - scale_w = (float(max_width)/width) or 1 - # the 'or 1' is so that a max of - # zero is ignored - scale_h = (float(max_height)/height) or 1 - else: - scale_w = 1.0 - scale_h = 1.0 - scale = min(scale_w, scale_h, 1.0) - new_width = int(width*scale) - new_height = int(height*scale) - - # TODO. Convert disk path to URL. - url = self.report.build_url_fname(orig_image_path, - None, self.uplink) - with Html("div", id="GalleryDisplay", - style='width: %dpx; height: %dpx' % ( - new_width, - new_height)) as mediadisplay: - summaryarea += mediadisplay - - # 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 _region_items: - ordered = Html("ol", class_="RegionBox") - mediadisplay += ordered - while len(_region_items) > 0: - (name, coord_x, coord_y, - width, height, linkurl - ) = _region_items.pop() - ordered += Html( - "li", - style="left:%d%%; " - "top:%d%%; " - "width:%d%%; " - "height:%d%%;" % ( - coord_x, coord_y, - width, height)) + ( - Html("a", name, - href=linkurl) - ) - - # display the image - if orig_image_path != newpath: - url = self.report.build_url_fname( - newpath, None, self.uplink) - mediadisplay += Html("a", href=url) + ( - Html("img", width=new_width, - height=new_height, src=url, - alt=esc_page_title) - ) - else: - dirname = tempfile.mkdtemp() - thmb_path = os.path.join(dirname, "document.png") - if run_thumbnailer(mime_type, - media_path_full(self.r_db, - media.get_path()), - thmb_path, 320): - try: - path = self.report.build_path( - "preview", media.get_handle()) - npath = os.path.join(path, media.get_handle()) - npath += ".png" - self.report.copy_file(thmb_path, npath) - path = npath - os.unlink(thmb_path) - except EnvironmentError: - path = os.path.join("images", "document.png") - else: - path = os.path.join("images", "document.png") - os.rmdir(dirname) - - with Html("div", id="GalleryDisplay") as mediadisplay: - summaryarea += mediadisplay - - img_url = self.report.build_url_fname(path, - None, - self.uplink) - if target_exists: - # TODO. Convert disk path to URL - url = self.report.build_url_fname(newpath, - None, - self.uplink) - hyper = Html("a", href=url, - title=esc_page_title) + ( - Html("img", src=img_url, - alt=esc_page_title) - ) - mediadisplay += hyper - else: - mediadisplay += Html("span", errormsg, - class_="MissingImage") - else: - with Html("div", id="GalleryDisplay") as mediadisplay: - summaryarea += mediadisplay - url = self.report.build_url_image("document.png", - "images", self.uplink) - mediadisplay += Html("img", src=url, - alt=esc_page_title, - title=esc_page_title) - - # media title - title = Html("h3", html_escape(self.page_title.strip()), - inline=True) - summaryarea += title - - # begin media table - with Html("table", class_="infolist gallery") as table: - summaryarea += table - - # Gramps ID - media_gid = media.gramps_id - if not self.noid and media_gid: - trow = Html("tr") + ( - Html("td", self._("Gramps ID"), - class_="ColumnAttribute", - inline=True), - Html("td", media_gid, class_="ColumnValue", - inline=True) - ) - table += trow - - # mime type - if mime_type: - trow = Html("tr") + ( - Html("td", self._("File Type"), - class_="ColumnAttribute", - inline=True), - Html("td", mime_type, class_="ColumnValue", - inline=True) - ) - table += trow - - # media date - date = media.get_date_object() - if date and date is not Date.EMPTY: - trow = Html("tr") + ( - Html("td", self._("Date"), class_="ColumnAttribute", - inline=True), - Html("td", self.rlocale.get_date(date), - class_="ColumnValue", - inline=True) - ) - table += trow - - # get media notes - notelist = self.display_note_list(media.get_note_list()) - if notelist is not None: - mediadetail += notelist - - # get attribute list - attrlist = media.get_attribute_list() - if attrlist: - attrsection, attrtable = self.display_attribute_header() - self.display_attr_list(attrlist, attrtable) - mediadetail += attrsection - - # get media sources - srclist = self.display_media_sources(media) - if srclist is not None: - mediadetail += srclist - - # get media references - reflist = self.display_bkref_list(Media, media_handle) - if reflist is not None: - mediadetail += reflist - - # add clearline for proper styling - # add footer section - footer = self.write_footer(ldatec) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(mediapage, output_file, sio, ldatec) - - def media_nav_link(self, handle, name, uplink=False): - """ - Creates the Media Page Navigation hyperlinks for Next and Prev - """ - url = self.report.build_url_fname_html(handle, "img", uplink) - name = html_escape(name) - return Html("a", name, name=name, id=name, href=url, - title=name, inline=True) - - def display_media_sources(self, photo): - """ - Display media sources - - @param: photo -- The source object (image, pdf, ...) - """ - list(map( - lambda i: self.bibli.add_reference( - self.r_db.get_citation_from_handle(i)), - photo.get_citation_list())) - sourcerefs = self.display_source_refs(self.bibli) - - # return source references to its caller - return sourcerefs - - def copy_source_file(self, handle, photo): - """ - Copy source file in the web tree. - - @param: handle -- Handle of the source - @param: photo -- The source object (image, pdf, ...) - """ - ext = os.path.splitext(photo.get_path())[1] - to_dir = self.report.build_path('images', handle) - newpath = os.path.join(to_dir, handle) + ext - - fullpath = media_path_full(self.r_db, photo.get_path()) - if not os.path.isfile(fullpath): - _WRONGMEDIAPATH.append([photo.get_gramps_id(), fullpath]) - return None - try: - mtime = os.stat(fullpath).st_mtime - 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) - new_file = os.path.join(self.html_dir, newpath) - shutil.copyfile(fullpath, new_file) - os.utime(new_file, (mtime, mtime)) - return newpath - except (IOError, OSError) as msg: - error = _("Missing media object:" - ) + "%s (%s)" % (photo.get_description(), - photo.get_gramps_id()) - self.r_user.warn(error, str(msg)) - return None - -class ThumbnailPreviewPage(BasePage): - """ - This class is responsible for displaying information about - the Thumbnails page. - """ - def __init__(self, report, title, cb_progress): - """ - @param: report -- The instance of the main report class - for this report - @param: title -- Is the title of the web page - @param: cb_progress -- The step used for the progress bar. - """ - BasePage.__init__(self, report, title) - self.create_thumbs_only = report.options['create_thumbs_only'] - # bug 8950 : it seems it's better to sort on desc + gid. - def sort_by_desc_and_gid(obj): - """ - Sort by media description and gramps ID - """ - return (obj.desc, obj.gramps_id) - - self.photo_keys = sorted(self.report.obj_dict[Media], - key=lambda x: sort_by_desc_and_gid( - self.r_db.get_media_from_handle(x))) - - if self.create_unused_media: - # add unused media - media_list = self.r_db.get_media_handles() - unused_media_handles = [] - for media_ref in media_list: - if media_ref not in self.report.obj_dict[Media]: - self.photo_keys.append(media_ref) - - media_list = [] - for person_handle in self.photo_keys: - photo = self.r_db.get_media_from_handle(person_handle) - if photo: - if photo.get_mime_type().startswith("image"): - media_list.append((photo.get_description(), person_handle, - photo)) - - if self.create_thumbs_only: - self.copy_thumbnail(person_handle, photo) - - media_list.sort(key=lambda x: self.rlocale.sort_key(x[0])) - - # Create thumbnail preview page... - output_file, sio = self.report.create_file("thumbnails") - thumbnailpage, head, body = self.write_header(self._("Thumbnails")) - - with Html("div", class_="content", id="Preview") as previewpage: - body += previewpage - - msg = self._("This page displays a indexed list " - "of all the media objects " - "in this database. It is sorted by media title. " - "There is an index " - "of all the media objects in this database. " - "Clicking on a thumbnail " - "will take you to that image’s page.") - previewpage += Html("p", msg, id="description") - - with Html("table", class_="calendar") as table: - previewpage += table - - thead = Html("thead") - table += thead - - # page title... - trow = Html("tr") - thead += trow - - trow += Html("th", self._("Thumbnail Preview"), - class_="monthName", colspan=7, inline=True) - - # table header cells... - trow = Html("tr") - thead += trow - - ltrs = [" ", " ", " ", - " ", " ", " ", " "] - for ltr in ltrs: - trow += Html("th", ltr, class_="weekend", inline=True) - - tbody = Html("tbody") - table += tbody - - index, indexpos = 1, 0 - num_of_images = len(media_list) - num_of_rows = ((num_of_images // 7) + 1) - num_of_cols = 7 - grid_row = 0 - while grid_row < num_of_rows: - trow = Html("tr", id="RowNumber: %08d" % grid_row) - tbody += trow - - cols = 0 - while cols < num_of_cols and indexpos < num_of_images: - ptitle = media_list[indexpos][0] - person_handle = media_list[indexpos][1] - photo = media_list[indexpos][2] - - # begin table cell and attach to table row(trow)... - tcell = Html("td", class_="highlight weekend") - trow += tcell - - # attach index number... - numberdiv = Html("div", class_="date") - tcell += numberdiv - - # attach anchor name to date cell in upper right - # corner of grid... - numberdiv += Html("a", index, name=index, title=index, - inline=True) - - # begin unordered list and - # attach to table cell(tcell)... - unordered = Html("ul") - tcell += unordered - - # create thumbnail - (real_path, - newpath) = self.report.prepare_copy_media(photo) - newpath = self.report.build_url_fname(newpath) - - list_html = Html("li") - unordered += list_html - - # attach thumbnail to list... - list_html += self.thumb_hyper_image(newpath, "img", - person_handle, - ptitle) - - index += 1 - indexpos += 1 - cols += 1 - grid_row += 1 - - # if last row is incomplete, finish it off? - if grid_row == num_of_rows and cols < num_of_cols: - for emptycols in range(cols, - num_of_cols): - trow += Html("td", class_="emptyDays", inline=True) - - # begin Thumbnail Reference section... - with Html("div", class_="subsection", id="references") as section: - body += section - section += Html("h4", self._("References"), inline=True) - - with Html("table", class_="infolist") as table: - section += table - - tbody = Html("tbody") - table += tbody - - index = 1 - for ptitle, person_handle, photo in media_list: - trow = Html("tr") - tbody += trow - - tcell1 = Html("td", - self.thumbnail_link(ptitle, index), - class_="ColumnRowLabel") - tcell2 = Html("td", ptitle, class_="ColumnName") - trow += (tcell1, tcell2) - - # increase index for row number... - index += 1 - - # increase progress meter... - cb_progress() - - # add body id element - body.attr = 'id ="ThumbnailPreview"' - - # add footer section - # add clearline for proper styling - footer = self.write_footer(None) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(thumbnailpage, output_file, sio, 0) - - - def thumbnail_link(self, name, index): - """ - creates a hyperlink for Thumbnail Preview Reference... - """ - return Html("a", index, title=html_escape(name), href="#%d" % index) - - def thumb_hyper_image(self, thumbnail_url, subdir, fname, name): - """ - eplaces media_link() because it doesn't work for this instance - """ - name = html_escape(name) - url = "/".join(self.report.build_subdirs(subdir, - fname) + [fname]) + self.ext - - with Html("div", class_="content", id="ThumbnailPreview") as section: - with Html("div", class_="snapshot") as snapshot: - section += snapshot - - with Html("div", class_="thumbnail") as thumbnail: - snapshot += thumbnail - - if not self.create_thumbs_only: - thumbnail_link = Html("a", href=url, title=name) + ( - Html("img", src=thumbnail_url, alt=name) - ) - else: - thumbnail_link = Html("img", src=thumbnail_url, - alt=name) - thumbnail += thumbnail_link - return section - -class DownloadPage(BasePage): - """ - This class is responsible for displaying information about the Download page - """ - def __init__(self, report, title): - """ - @param: report -- The instance of the main report class for this report - @param: title -- Is the title of the web page - """ - BasePage.__init__(self, report, title) - - # do NOT include a Download Page - if not self.report.inc_download: - return - - # menu options for class - # download and description #1 - - dlfname1 = self.report.dl_fname1 - dldescr1 = self.report.dl_descr1 - - # download and description #2 - dlfname2 = self.report.dl_fname2 - dldescr2 = self.report.dl_descr2 - - # if no filenames at all, return??? - if dlfname1 or dlfname2: - - output_file, sio = self.report.create_file("download") - downloadpage, head, body = self.write_header(self._('Download')) - - # begin download page and table - with Html("div", class_="content", id="Download") as download: - body += download - - msg = self._("This page is for the user/ creator " - "of this Family Tree/ Narrative website " - "to share a couple of files with you " - "regarding their family. If there are " - "any files listed " - "below, clicking on them will allow you " - "to download them. The " - "download page and files have the same " - "copyright as the remainder " - "of these web pages.") - download += Html("p", msg, id="description") - - # begin download table and table head - with Html("table", class_="infolist download") as table: - download += table - - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - trow.extend( - Html("th", label, class_="Column" + colclass, - inline=True) - for (label, colclass) in [ - (self._("File Name"), "Filename"), - (self._("Description"), "Description"), - (self._("Last Modified"), "Modified")] - ) - # table body - tbody = Html("tbody") - table += tbody - - # if dlfname1 is not None, show it??? - if dlfname1: - - trow = Html("tr", id='Row01') - tbody += trow - - fname = os.path.basename(dlfname1) - # TODO dlfname1 is filename, convert disk path to URL - tcell = Html("td", class_="ColumnFilename") + ( - Html("a", fname, href=dlfname1, - title=html_escape(dldescr1)) - ) - trow += tcell - - dldescr1 = dldescr1 or " " - trow += Html("td", dldescr1, - class_="ColumnDescription", inline=True) - - tcell = Html("td", class_="ColumnModified", inline=True) - trow += tcell - if os.path.exists(dlfname1): - modified = os.stat(dlfname1).st_mtime - last_mod = datetime.datetime.fromtimestamp(modified) - tcell += last_mod - else: - tcell += " " - - # if download filename #2, show it??? - if dlfname2: - - # begin row #2 - trow = Html("tr", id='Row02') - tbody += trow - - fname = os.path.basename(dlfname2) - tcell = Html("td", class_="ColumnFilename") + ( - Html("a", fname, href=dlfname2, - title=html_escape(dldescr2)) - ) - trow += tcell - - dldescr2 = dldescr2 or " " - trow += Html("td", dldescr2, - class_="ColumnDescription", inline=True) - - tcell = Html("td", id='Col04', - class_="ColumnModified", inline=True) - trow += tcell - if os.path.exists(dlfname2): - modified = os.stat(dlfname2).st_mtime - last_mod = datetime.datetime.fromtimestamp(modified) - tcell += last_mod - else: - tcell += " " - - # clear line for proper styling - # create footer section - footer = self.write_footer(None) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(downloadpage, output_file, sio, 0) - -class ContactPage(BasePage): - """ - This class is responsible for displaying information about the 'Researcher' - """ - def __init__(self, report, title): - """ - @param: report -- The instance of the main report class for this report - @param: title -- Is the title of the web page - """ - BasePage.__init__(self, report, title) - - output_file, sio = self.report.create_file("contact") - contactpage, head, body = self.write_header(self._('Contact')) - - # begin contact division - with Html("div", class_="content", id="Contact") as section: - body += section - - # begin summaryarea division - with Html("div", id='summaryarea') as summaryarea: - section += summaryarea - - contactimg = self.add_image('contactimg', 200) - if contactimg is not None: - summaryarea += contactimg - - # get researcher information - res = get_researcher() - - with Html("div", id='researcher') as researcher: - summaryarea += researcher - - if res.name: - res.name = res.name.replace(',,,', '') - researcher += Html("h3", res.name, inline=True) - if res.addr: - researcher += Html("span", res.addr, - id='streetaddress', inline=True) - if res.locality: - researcher += Html("span", res.locality, - id="locality", inline=True) - text = "".join([res.city, res.state, res.postal]) - if text: - city = Html("span", res.city, id='city', inline=True) - state = Html("span", res.state, id='state', inline=True) - postal = Html("span", res.postal, id='postalcode', - inline=True) - researcher += (city, state, postal) - if res.country: - researcher += Html("span", res.country, - id='country', inline=True) - if res.email: - researcher += Html("span", id='email') + ( - Html("a", res.email, - href='mailto:%s' % res.email, inline=True) - ) - - # add clear line for proper styling - summaryarea += FULLCLEAR - - note_id = report.options['contactnote'] - if note_id: - note = self.r_db.get_note_from_gramps_id(note_id) - note_text = self.get_note_format(note, False) - - # attach note - summaryarea += note_text - - # add clearline for proper styling - # add footer section - footer = self.write_footer(None) - body += (FULLCLEAR, footer) - - # send page out for porcessing - # and close the file - self.xhtml_writer(contactpage, output_file, sio, 0) - -################################################# -# -# creates the Individual List Page and IndividualPages -# -################################################# -class PersonPages(BasePage): - """ - This class is responsible for displaying information about the 'Person' - database objects. It displays this information under the 'Individuals' - tab. It is told by the 'add_instances' call which 'Person's to display, - and remembers the list of persons. A single call to 'display_pages' - displays both the Individual List (Index) page and all the Individual - pages. - - The base class 'BasePage' is initialised once for each page that is - displayed. - """ - def __init__(self, report): - """ - @param: report -- The instance of the main report class for this report - """ - BasePage.__init__(self, report, title="") - self.ind_dict = defaultdict(set) - self.mapservice = None - self.sort_name = None - self.googleopts = None - self.googlemapkey = None - self.birthorder = None - self.person = None - self.familymappages = None - self.rel_class = None - self.placemappages = None - self.name = None - - def display_pages(self, title): - """ - Generate and output the pages under the Individuals tab, namely the - individual index and the individual pages. - - @param: title -- Is the title of the web page - """ - LOG.debug("obj_dict[Person]") - for item in self.report.obj_dict[Person].items(): - LOG.debug(" %s", str(item)) - with self.r_user.progress(_("Narrated Web Site Report"), - _('Creating individual pages'), - len(self.report.obj_dict[Person]) + 1 - ) as step: - self.individuallistpage(self.report, title, - self.report.obj_dict[Person].keys()) - for person_handle in sorted(self.report.obj_dict[Person]): - step() - person = self.r_db.get_person_from_handle(person_handle) - self.individualpage(self.report, title, person) - -################################################# -# -# creates the Individual List Page -# -################################################# - def individuallistpage(self, report, title, ppl_handle_list): - """ - Creates an individual page - - @param: report -- The instance of the main report class - for this report - @param: title -- Is the title of the web page - @param: ppl_handle_list -- The list of people for whom we need - to create a page. - """ - BasePage.__init__(self, report, title) - prev_letter = " " - - # plugin variables for this module - showbirth = report.options['showbirth'] - showdeath = report.options['showdeath'] - showpartner = report.options['showpartner'] - showparents = report.options['showparents'] - - output_file, sio = self.report.create_file("individuals") - indlistpage, head, body = self.write_header(self._("Individuals")) - date = 0 - - # begin Individuals division - with Html("div", class_="content", id="Individuals") as individuallist: - body += individuallist - - # Individual List page message - msg = self._("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.") - individuallist += Html("p", msg, id="description") - - # add alphabet navigation - index_list = get_first_letters(self.r_db, ppl_handle_list, - _KEYPERSON, rlocale=self.rlocale) - alpha_nav = alphabet_navigation(index_list, self.rlocale) - if alpha_nav is not None: - individuallist += alpha_nav - - # begin table and table head - with Html("table", - class_="infolist primobjlist IndividualList") as table: - individuallist += table - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - # show surname and first name - trow += Html("th", self._("Surname"), class_="ColumnSurname", - inline=True) - trow += Html("th", self._("Given Name"), class_="ColumnName", - inline=True) - - if showbirth: - trow += Html("th", self._("Birth"), class_="ColumnDate", - inline=True) - - if showdeath: - trow += Html("th", self._("Death"), class_="ColumnDate", - inline=True) - - if showpartner: - trow += Html("th", self._("Partner"), - class_="ColumnPartner", - inline=True) - - if showparents: - trow += Html("th", self._("Parents"), - class_="ColumnParents", - inline=True) - - tbody = Html("tbody") - table += tbody - - ppl_handle_list = sort_people(self.r_db, ppl_handle_list, - self.rlocale) - first = True - for (surname, handle_list) in ppl_handle_list: - - if surname and not surname.isspace(): - letter = get_index_letter(first_letter(surname), index_list, - self.rlocale) - else: - letter = ' ' - surname = self._("") - - first_surname = True - for person_handle in sorted(handle_list, - key=self.sort_on_name_and_grampsid): - person = self.r_db.get_person_from_handle(person_handle) - if person.get_change_time() > date: - date = person.get_change_time() - - # surname column - trow = Html("tr") - tbody += trow - tcell = Html("td", class_="ColumnSurname", inline=True) - trow += tcell - - if first or primary_difference(letter, prev_letter, - self.rlocale): - first = False - first_surname = False - prev_letter = letter - trow.attr = 'class = "BeginSurname"' - tcell += Html( - "a", html_escape(surname), name=letter, - id_=letter, - title=self._("Surnames %(surname)s beginning " - "with letter %(letter)s") % - {'surname' : surname, - 'letter' : letter}) - elif first_surname: - first_surname = False - tcell += Html("a", html_escape(surname), - title=self._("Surnames") + " " + surname) - else: - tcell += " " - - # firstname column - link = self.new_person_link(person_handle, person=person, - name_style=_NAME_STYLE_FIRST) - trow += Html("td", link, class_="ColumnName") - - # birth column - if showbirth: - tcell = Html("td", class_="ColumnBirth", inline=True) - trow += tcell - - birth_date = _find_birth_date(self.r_db, person) - if birth_date is not None: - if birth_date.fallback: - tcell += Html('em', - self.rlocale.get_date(birth_date), - inline=True) - else: - tcell += self.rlocale.get_date(birth_date) - else: - tcell += " " - - # death column - if showdeath: - tcell = Html("td", class_="ColumnDeath", inline=True) - trow += tcell - - death_date = _find_death_date(self.r_db, person) - if death_date is not None: - if death_date.fallback: - tcell += Html('em', - self.rlocale.get_date(death_date), - inline=True) - else: - tcell += self.rlocale.get_date(death_date) - else: - tcell += " " - - # partner column - if showpartner: - - family_list = person.get_family_handle_list() - first_family = True - #partner_name = None - tcell = () - if family_list: - for family_handle in family_list: - family = self.r_db.get_family_from_handle( - family_handle) - partner_handle = utils.find_spouse( - person, family) - if partner_handle: - if not first_family: - # have to do this to get the comma on - # the same line as the link - if isinstance(tcell[-1], Html): - # tcell is an instance of Html (or - # of a subclass thereof) - tcell[-1].inside += "," - else: - tcell = tcell[:-1] + ( - (tcell[-1] + ", "),) - # Have to manipulate as tuples so that - # subsequent people are not nested - # within the first link - tcell += ( - self.new_person_link(partner_handle),) - first_family = False - else: - tcell = " " - trow += Html("td", class_="ColumnPartner") + tcell - - # parents column - if showparents: - - parent_hdl_list = person.get_parent_family_handle_list() - if parent_hdl_list: - parent_handle = parent_hdl_list[0] - family = self.r_db.get_family_from_handle( - parent_handle) - father_handle = family.get_father_handle() - mother_handle = family.get_mother_handle() - if father_handle: - father = self.r_db.get_person_from_handle( - father_handle) - else: - father = None - if mother_handle: - mother = self.r_db.get_person_from_handle( - mother_handle) - else: - mother = None - if father: - father_name = self.get_name(father) - if mother: - mother_name = self.get_name(mother) - samerow = False - if mother and father: - tcell = (Html("span", father_name, - class_="father fatherNmother", - inline=True), - Html("span", mother_name, - class_="mother", inline=True)) - elif mother: - tcell = Html("span", mother_name, - class_="mother", inline=True) - elif father: - tcell = Html("span", father_name, - class_="father", inline=True) - else: - tcell = " " - samerow = True - else: - tcell = " " - samerow = True - trow += Html("td", class_="ColumnParents", - inline=samerow) + tcell - - # create clear line for proper styling - # create footer section - footer = self.write_footer(date) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(indlistpage, output_file, sio, date) - -################################################# -# -# creates an Individual Page -# -################################################# - gender_map = { - Person.MALE : _('male'), - Person.FEMALE : _('female'), - Person.UNKNOWN : _('unknown'), - } - - def individualpage(self, report, title, person): - """ - Creates an individual page - - @param: report -- The instance of the main report class for this report - @param: title -- Is the title of the web page - @param: person -- The person to use for this page. - """ - BasePage.__init__(self, report, title, person.get_gramps_id()) - place_lat_long = [] - - self.person = person - self.bibli = Bibliography() - self.sort_name = self.get_name(person) - self.name = self.get_name(person) - - date = self.person.get_change_time() - - # to be used in the Family Map Pages... - self.familymappages = self.report.options['familymappages'] - self.placemappages = self.report.options['placemappages'] - self.mapservice = self.report.options['mapservice'] - self.googleopts = self.report.options['googleopts'] - self.googlemapkey = self.report.options['googlemapkey'] - - # decide if we will sort the birth order of siblings... - self.birthorder = self.report.options['birthorder'] - - # get the Relationship Calculator so that we can determine - # bio, half, step- siblings for use in display_ind_parents() ... - self.rel_class = self.report.rel_class - - output_file, sio = self.report.create_file(person.get_handle(), "ppl") - self.uplink = True - indivdetpage, head, body = self.write_header(self.sort_name) - - # attach the ancestortree style sheet if ancestor - # graph is being created? - if self.report.options["ancestortree"]: - if self.usecms: - fname = "/".join([self.target_uri, "css", "ancestortree.css"]) - else: - fname = "/".join(["css", "ancestortree.css"]) - url = self.report.build_url_fname(fname, None, self.uplink) - head += Html("link", href=url, type="text/css", media="screen", - rel="stylesheet") - - # begin individualdetail division - with Html("div", class_="content", - id='IndividualDetail') as individualdetail: - body += individualdetail - - # display a person's general data - thumbnail, name, summary = self.display_ind_general() - if thumbnail is not None: - individualdetail += thumbnail - individualdetail += (name, summary) - - # display a person's events - sect2 = self.display_ind_events(place_lat_long) - if sect2 is not None: - individualdetail += sect2 - - # display relationship to the center person - sect3 = self.display_ind_center_person() - if sect3 is not None: - individualdetail += sect3 - - # display parents - sect4 = self.display_ind_parents() - if sect4 is not None: - individualdetail += sect4 - - # display relationships - relationships = self.display_relationships(self.person, - place_lat_long) - if relationships is not None: - individualdetail += relationships - - # display LDS ordinance - sect5 = self.display_lds_ordinance(self.person) - if sect5 is not None: - individualdetail += sect5 - - # display address(es) and show sources - sect6 = self.display_addr_list(self.person.get_address_list(), True) - if sect6 is not None: - individualdetail += sect6 - - photo_list = self.person.get_media_list() - media_list = photo_list[:] - - # if Family Pages are not being created, then include the Family - # Media objects? There is no reason to add these objects to the - # Individual Pages... - if not self.inc_families: - for handle in self.person.get_family_handle_list(): - family = self.r_db.get_family_from_handle(handle) - if family: - media_list += family.get_media_list() - for evt_ref in family.get_event_ref_list(): - event = self.r_db.get_event_from_handle(evt_ref.ref) - media_list += event.get_media_list() - - # if the Event Pages are not being created, then include the Event - # Media objects? There is no reason to add these objects to the - # Individual Pages... - if not self.inc_events: - for evt_ref in self.person.get_primary_event_ref_list(): - event = self.r_db.get_event_from_handle(evt_ref.ref) - if event: - media_list += event.get_media_list() - - # display additional images as gallery - sect7 = self.disp_add_img_as_gallery(media_list, person) - if sect7 is not None: - individualdetail += sect7 - - # display Narrative Notes - notelist = person.get_note_list() - sect8 = self.display_note_list(notelist) - if sect8 is not None: - individualdetail += sect8 - - # display attributes - attrlist = person.get_attribute_list() - if attrlist: - attrsection, attrtable = self.display_attribute_header() - self.display_attr_list(attrlist, attrtable) - individualdetail += attrsection - - # display web links - sect10 = self.display_url_list(self.person.get_url_list()) - if sect10 is not None: - individualdetail += sect10 - - # display associations - assocs = person.get_person_ref_list() - if assocs: - individualdetail += self.display_ind_associations(assocs) - - # for use in family map pages... - if len(place_lat_long) > 0: - if self.report.options["familymappages"]: - # save output_file, string_io and cur_fname - # before creating a new page - sof = output_file - sstring_io = sio - sfname = self.report.cur_fname - individualdetail += self.__display_family_map( - person, place_lat_long) - # restore output_file, string_io and cur_fname - # after creating a new page - output_file = sof - sio = sstring_io - self.report.cur_fname = sfname - - # display pedigree - sect13 = self.display_ind_pedigree() - if sect13 is not None: - individualdetail += sect13 - - # display ancestor tree - if report.options['ancestortree']: - sect14 = self.display_tree() - if sect14 is not None: - individualdetail += sect14 - - # display source references - sect14 = self.display_ind_sources(person) - if sect14 is not None: - individualdetail += sect14 - - # add clearline for proper styling - # create footer section - footer = self.write_footer(date) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(indivdetpage, output_file, sio, date) - - def __create_family_map(self, person, place_lat_long): - """ - creates individual family map page - - @param: person -- person from database - @param: place_lat_long -- for use in Family Map Pages - """ - if not place_lat_long: - return - - output_file, sio = self.report.create_file(person.get_handle(), "maps") - self.uplink = True - familymappage, head, body = self.write_header(self._("Family Map")) - - minx, maxx = Decimal("0.00000001"), Decimal("0.00000001") - miny, maxy = Decimal("0.00000001"), Decimal("0.00000001") - xwidth, yheight = [], [] - midx_, midy_, spanx, spany = [None]*4 - - number_markers = len(place_lat_long) - if number_markers > 1: - for (latitude, longitude, placetitle, handle, - date, etype) in place_lat_long: - xwidth.append(latitude) - yheight.append(longitude) - xwidth.sort() - yheight.sort() - - minx = xwidth[0] if xwidth[0] else minx - maxx = xwidth[-1] if xwidth[-1] else maxx - minx, maxx = Decimal(minx), Decimal(maxx) - midx_ = str(Decimal((minx + maxx) /2)) - - miny = yheight[0] if yheight[0] else miny - maxy = yheight[-1] if yheight[-1] else maxy - miny, maxy = Decimal(miny), Decimal(maxy) - midy_ = str(Decimal((miny + maxy) /2)) - - midx_, midy_ = conv_lat_lon(midx_, midy_, "D.D8") - - # get the integer span of latitude and longitude - spanx = int(maxx - minx) - spany = int(maxy - miny) - - # set zoom level based on span of Longitude? - tinyset = [value for value in (-3, -2, -1, 0, 1, 2, 3)] - smallset = [value for value in (-4, -5, -6, -7, 4, 5, 6, 7)] - middleset = [value for value in (-8, -9, -10, -11, 8, 9, 10, 11)] - largeset = [value for value in (-11, -12, -13, -14, -15, -16, - -17, 11, 12, 13, 14, 15, 16, 17)] - - if spany in tinyset or spany in smallset: - zoomlevel = 6 - elif spany in middleset: - zoomlevel = 5 - elif spany in largeset: - zoomlevel = 4 - else: - zoomlevel = 3 - - # 0 = latitude, 1 = longitude, 2 = place title, - # 3 = handle, and 4 = date, 5 = event type... - # being sorted by date, latitude, and longitude... - place_lat_long = sorted(place_lat_long, key=itemgetter(4, 0, 1)) - - # for all plugins - # if family_detail_page - # if active - # call_(report, up, head) - - # add narrative-maps style sheet - if self.usecms: - fname = "/".join([self.target_uri, "css", "narrative-maps.css"]) - else: - fname = "/".join(["css", "narrative-maps.css"]) - url = self.report.build_url_fname(fname, None, self.uplink) - head += Html("link", href=url, type="text/css", media="screen", - rel="stylesheet") - - # add MapService specific javascript code - if self.mapservice == "Google": - src_js = GOOGLE_MAPS + "api/js?sensor=false" - if self.googlemapkey: - src_js += "&key=" + self.googlemapkey - head += Html("script", type="text/javascript", - src=src_js, inline=True) - else: - url = self.secure_mode - url += "maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" - head += Html("link", href=url, type="text/javascript", - rel="stylesheet") - src_js = self.secure_mode - src_js += "ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" - head += Html("script", type="text/javascript", - src=src_js, inline=True) - src_js = self.secure_mode - src_js += "openlayers.org/en/v3.17.1/build/ol.js" - head += Html("script", type="text/javascript", - src=src_js, inline=True) - url = self.secure_mode - url += "openlayers.org/en/v3.17.1/css/ol.css" - head += Html("link", href=url, type="text/javascript", - rel="stylesheet") - src_js = self.secure_mode - src_js += "maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" - head += Html("script", type="text/javascript", - src=src_js, inline=True) - - if number_markers > 0: - tracelife = "[" - seq_ = 1 - - for index in range(0, (number_markers - 1)): - (latitude, longitude, placetitle, handle, date, - etype) = place_lat_long[index] - - # are we using Google? - if self.mapservice == "Google": - - # are we creating Family Links? - if self.googleopts == "FamilyLinks": - tracelife += """ - new google.maps.LatLng(%s, %s),""" % (latitude, longitude) - - # are we creating Drop Markers or Markers? - elif self.googleopts in ["Drop", "Markers"]: - tracelife += """ - ['%s', %s, %s, %d],""" % (placetitle.replace("'", "\\'"), latitude, - longitude, seq_) - - # are we using OpenStreetMap? - else: - tracelife += """ - [%f, %f, \'%s\'],""" % (float(longitude), float(latitude), - placetitle.replace("'", "\\'")) - - seq_ += 1 - # FIXME: The last element in the place_lat_long list is treated - # specially, and the code above is apparently repeated so as to - # avoid a comma at the end, and get the right closing. This is very - # ugly. - (latitude, longitude, placetitle, handle, date, - etype) = place_lat_long[-1] - - # are we using Google? - if self.mapservice == "Google": - - # are we creating Family Links? - if self.googleopts == "FamilyLinks": - tracelife += """ - new google.maps.LatLng(%s, %s) - ];""" % (latitude, longitude) - - # are we creating Drop Markers or Markers? - elif self.googleopts in ["Drop", "Markers"]: - tracelife += """ - ['%s', %s, %s, %d] - ];""" % (placetitle.replace("'", "\\'"), latitude, longitude, seq_) - - # are we using OpenStreetMap? - elif self.mapservice == "OpenStreetMap": - tracelife += """ - [%f, %f, \'%s\'] - ];""" % (float(longitude), float(latitude), placetitle.replace("'", "\\'")) - - # begin MapDetail division... - with Html("div", class_="content", id="FamilyMapDetail") as mapdetail: - body += mapdetail - - # add page title - mapdetail += Html("h3", - html_escape( - self._("Tracking %s") % self.get_name(person)), - inline=True) - - # page description - msg = self._("This map page represents that person " - "and any descendants with " - "all of their event/ places. If you place your mouse over " - "the marker it will display the place name. " - "The markers and the Reference " - "list are sorted in date order (if any?). " - "Clicking on a place’s " - "name in the Reference section will take you " - "to that place’s page.") - mapdetail += Html("p", msg, id="description") - - # this is the style element where the Map is held in the CSS... - with Html("div", id="map_canvas") as canvas: - mapdetail += canvas - - # begin javascript inline code... - with Html("script", deter="deter", - style='width =100%; height =100%;', - type="text/javascript", indent=False) as jsc: - head += jsc - - # Link to Gramps marker - fname = "/".join(['images', 'marker.png']) - marker_path = self.report.build_url_image("marker.png", - "images", - self.uplink) - - jsc += MARKER_PATH % marker_path - # are we using Google? - if self.mapservice == "Google": - - # are we creating Family Links? - if self.googleopts == "FamilyLinks": - if midy_ == None: - jsc += FAMILYLINKS % (tracelife, latitude, - longitude, int(10)) - else: - jsc += FAMILYLINKS % (tracelife, midx_, midy_, - zoomlevel) - - # are we creating Drop Markers? - elif self.googleopts == "Drop": - if midy_ == None: - jsc += DROPMASTERS % (tracelife, latitude, - longitude, int(10)) - else: - jsc += DROPMASTERS % (tracelife, midx_, midy_, - zoomlevel) - - # we are creating Markers only... - else: - if midy_ == None: - jsc += MARKERS % (tracelife, latitude, - longitude, int(10)) - else: - jsc += MARKERS % (tracelife, midx_, midy_, - zoomlevel) - - # we are using OpenStreetMap... - else: - if midy_ == None: - jsc += OSM_MARKERS % (tracelife, - longitude, - latitude, 10) - else: - jsc += OSM_MARKERS % (tracelife, midy_, midx_, - zoomlevel) - - # if Google and Drop Markers are selected, - # then add "Drop Markers" button? - if self.mapservice == "Google" and self.googleopts == "Drop": - mapdetail += Html("button", _("Drop Markers"), - id="drop", onclick="drop()", inline=True) - - # add div for popups. - with Html("div", id="popup", inline=True) as popup: - mapdetail += popup - - # begin place reference section and its table... - with Html("div", class_="subsection", id="references") as section: - mapdetail += section - section += Html("h4", self._("References"), inline=True) - - with Html("table", class_="infolist") as table: - section += table - - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - trow.extend( - Html("th", label, class_=colclass, inline=True) - for (label, colclass) in [ - (_("Date"), "ColumnDate"), - (_("Place Title"), "ColumnPlace"), - (_("Event Type"), "ColumnType") - ] - ) - - tbody = Html("tbody") - table += tbody - - for (latitude, longitude, placetitle, handle, date, - etype) in place_lat_long: - trow = Html("tr") - tbody += trow - - trow.extend( - Html("td", data, class_=colclass, inline=True) - for data, colclass in [ - (date, "ColumnDate"), - (self.place_link(handle, placetitle, - uplink=True), - "ColumnPlace"), - (str(etype), "ColumnType") - ] - ) - - # add body id for this page... - body.attr = 'id ="FamilyMap" onload ="initialize()"' - - # add clearline for proper styling - # add footer section - footer = self.write_footer(None) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(familymappage, output_file, sio, 0) - - def __display_family_map(self, person, place_lat_long): - """ - Create the family map link - - @param: person -- The person to set in the box - @param: place_lat_long -- The center of the box - """ - # create family map page - self.__create_family_map(person, place_lat_long) - - # begin family map division plus section title - with Html("div", class_="subsection", id="familymap") as familymap: - familymap += Html("h4", self._("Family Map"), inline=True) - - # add family map link - person_handle = person.get_handle() - url = self.report.build_url_fname_html(person_handle, "maps", True) - familymap += self.family_map_link(person_handle, url) - - # return family map link to its caller - return familymap - - def draw_box(self, center, col, person): - """ - Draw the box around the AncestorTree Individual name box... - - @param: center -- The center of the box - @param: col -- The generation number - @param: person -- The person to set in the box - """ - top = center - _HEIGHT/2 - xoff = _XOFFSET+col*(_WIDTH+_HGAP) - sex = person.gender - if sex == Person.MALE: - divclass = "male" - elif sex == Person.FEMALE: - divclass = "female" - else: - divclass = "unknown" - - boxbg = Html("div", class_="boxbg %s AncCol%s" % (divclass, col), - style="top: %dpx; left: %dpx;" % (top, xoff+1) - ) - - person_name = self.get_name(person) - # This does not use [new_]person_link because the requirements are - # unique - result = self.report.obj_dict.get(Person).get(person.handle) - if result is None or result[0] == "": - # The person is not included in the webreport or there is no link - # to them - boxbg += Html("span", person_name, class_="unlinked", inline=True) - else: - thumbnail_url = None - if self.create_media and col < 5: - photolist = person.get_media_list() - if photolist: - photo_handle = photolist[0].get_reference_handle() - photo = self.r_db.get_media_from_handle(photo_handle) - mime_type = photo.get_mime_type() - if mime_type: - region = self.media_ref_region_to_object(photo_handle, - person) - if region: - # make a thumbnail of this region - newpath = self.copy_thumbnail( - photo_handle, photo, region) - # TODO. Check if build_url_fname can be used. - newpath = "/".join(['..']*3 + [newpath]) - if win(): - newpath = newpath.replace('\\', "/") - thumbnail_url = newpath - else: - (photo_url, - thumbnail_url) = self.report.prepare_copy_media( - photo) - thumbnail_url = "/".join(['..']*3 + [thumbnail_url]) - if win(): - thumbnail_url = thumbnail_url.replace('\\', "/") - url = self.report.build_url_fname_html(person.handle, "ppl", True) - birth = death = "" - bd_event = get_birth_or_fallback(self.r_db, person) - if bd_event: - birth = self.rlocale.get_date(bd_event.get_date_object()) - dd_event = get_death_or_fallback(self.r_db, person) - if dd_event: - death = self.rlocale.get_date(dd_event.get_date_object()) - if death == "": - death = "..." - value = person_name + "
    *", birth, "
    +", death - if thumbnail_url is None: - boxbg += Html("a", href=url, class_="noThumb") + value - else: - thumb = Html("span", class_="thumbnail") + ( - Html("img", src=thumbnail_url, alt="Image: " + person_name)) - boxbg += Html("a", href=url) + thumb + value - shadow = Html( - "div", class_="shadow", inline=True, - style="top: %dpx; left: %dpx;" % (top + _SHADOW, xoff + _SHADOW)) - - return [boxbg, shadow] - - def extend_line(self, coord_y0, coord_x0): - """ - Draw and extended line - - @param: coord_y0 -- The starting point - @param: coord_x0 -- The end of the line - """ - style = "top: %dpx; left: %dpx; width: %dpx" - ext_bv = Html("div", class_="bvline", inline=True, - style=style % (coord_y0, coord_x0, _HGAP/2) - ) - ext_gv = Html("div", class_="gvline", inline=True, - style=style % (coord_y0+_SHADOW, - coord_x0, _HGAP/2+_SHADOW) - ) - return [ext_bv, ext_gv] - - def connect_line(self, coord_y0, coord_y1, col): - """ - We need to draw a line between to points - - @param: coord_y0 -- The starting point - @param: coord_y1 -- The end of the line - @param: col -- The generation number - """ - coord_y = min(coord_y0, coord_y1) - stylew = "top: %dpx; left: %dpx; width: %dpx;" - styleh = "top: %dpx; left: %dpx; height: %dpx;" - coord_x0 = _XOFFSET + col * _WIDTH + (col-1)*_HGAP + _HGAP/2 - cnct_bv = Html("div", class_="bvline", inline=True, - style=stylew % (coord_y1, coord_x0, _HGAP/2)) - cnct_gv = Html("div", class_="gvline", inline=True, - style=stylew % (coord_y1+_SHADOW, - coord_x0+_SHADOW, - _HGAP/2+_SHADOW)) - cnct_bh = Html("div", class_="bhline", inline=True, - style=styleh % (coord_y, coord_x0, - abs(coord_y0-coord_y1))) - cnct_gh = Html("div", class_="gvline", inline=True, - style=styleh % (coord_y+_SHADOW, - coord_x0+_SHADOW, - abs(coord_y0-coord_y1))) - return [cnct_bv, cnct_gv, cnct_bh, cnct_gh] - - def draw_connected_box(self, center1, center2, col, handle): - """ - Draws the connected box for Ancestor Tree on the Individual Page - - @param: center1 -- The first box to connect - @param: center2 -- The destination box to draw - @param: col -- The generation number - @param: handle -- The handle of the person to set in the new box - """ - box = [] - if not handle: - return box - person = self.r_db.get_person_from_handle(handle) - box = self.draw_box(center2, col, person) - box += self.connect_line(center1, center2, col) - return box - - def display_tree(self): - """ - Display the Ancestor Tree - """ - tree = [] - if not self.person.get_main_parents_family_handle(): - return None - - generations = self.report.options['graphgens'] - max_in_col = 1 << (generations-1) - max_size = _HEIGHT*max_in_col + _VGAP*(max_in_col+1) - center = int(max_size/2) - - with Html("div", id="tree", class_="subsection") as tree: - tree += Html("h4", self._('Ancestors'), inline=True) - with Html("div", id="treeContainer", - style="width:%dpx; height:%dpx;" % ( - _XOFFSET+(generations)*_WIDTH+(generations-1)*_HGAP, - max_size) - ) as container: - tree += container - container += self.draw_tree(1, generations, max_size, - 0, center, self.person.handle) - return tree - - def draw_tree(self, gen_nr, maxgen, max_size, old_center, - new_center, person_handle): - """ - Draws the Ancestor Tree - - @param: gen_nr -- The generation number to draw - @param: maxgen -- The maximum number of generations to draw - @param: max_size -- The maximum size of the drawing area - @param: old_center -- The position of the old box - @param: new_center -- The position of the new box - @param: person_handle -- The handle of the person to draw - """ - tree = [] - if gen_nr > maxgen: - return tree - gen_offset = int(max_size / pow(2, gen_nr+1)) - if person_handle: - person = self.r_db.get_person_from_handle(person_handle) - else: - person = None - if not person: - return tree - - if gen_nr == 1: - tree = self.draw_box(new_center, 0, person) - else: - tree = self.draw_connected_box(old_center, new_center, - gen_nr-1, person_handle) - - if gen_nr == maxgen: - return tree - - family_handle = person.get_main_parents_family_handle() - if family_handle: - line_offset = _XOFFSET + gen_nr*_WIDTH + (gen_nr-1)*_HGAP - tree += self.extend_line(new_center, line_offset) - - family = self.r_db.get_family_from_handle(family_handle) - - f_center = new_center-gen_offset - f_handle = family.get_father_handle() - tree += self.draw_tree(gen_nr+1, maxgen, max_size, - new_center, f_center, f_handle) - - m_center = new_center+gen_offset - m_handle = family.get_mother_handle() - tree += self.draw_tree(gen_nr+1, maxgen, max_size, - new_center, m_center, m_handle) - return tree - - def display_ind_associations(self, assoclist): - """ - Display an individual's associations - - @param: assoclist -- The list of persons for association - """ - # begin Associations division - with Html("div", class_="subsection", id="Associations") as section: - section += Html("h4", self._('Associations'), inline=True) - - with Html("table", class_="infolist assoclist") as table: - section += table - - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - assoc_row = [ - (self._("Person"), 'Person'), - (self._('Relationship'), 'Relationship'), - (self._("Notes"), 'Notes'), - (self._("Sources"), 'Sources'), - ] - - trow.extend( - Html("th", label, class_="Column" + colclass, inline=True) - for (label, colclass) in assoc_row) - - tbody = Html("tbody") - table += tbody - - for person_ref in assoclist: - trow = Html("tr") - tbody += trow - - person_lnk = self.new_person_link(person_ref.ref, - uplink=True) - - index = 0 - for data in [ - person_lnk, - person_ref.get_relation(), - self.dump_notes(person_ref.get_note_list()), - self.get_citation_links( - person_ref.get_citation_list()), - ]: - - # get colclass from assoc_row - colclass = assoc_row[index][1] - - trow += Html("td", data, class_="Column" + colclass, - inline=True) - index += 1 - - # return section to its callers - return section - - def display_ind_pedigree(self): - """ - Display an individual's pedigree - """ - birthorder = self.report.options["birthorder"] - - # Define helper functions - def children_ped(ol_html): - """ - Create a children list - - @param: ol_html -- The html element to complete - """ - if family: - childlist = family.get_child_ref_list() - - childlist = [child_ref.ref for child_ref in childlist] - children = add_birthdate(self.r_db, childlist, self.rlocale) - - if birthorder: - children = sorted(children) - - for birthdate, birth, death, handle in children: - if handle == self.person.get_handle(): - child_ped(ol_html) - elif handle: - child = self.r_db.get_person_from_handle(handle) - if child: - ol_html += Html("li") + self.pedigree_person(child) - else: - child_ped(ol_html) - return ol_html - - def child_ped(ol_html): - """ - Create a child element list - - @param: ol_html -- The html element to complete - """ - with Html("li", self.name, class_="thisperson") as pedfam: - family = self.pedigree_family() - if family: - pedfam += Html("ol", class_="spouselist") + family - return ol_html + pedfam - # End of helper functions - - parent_handle_list = self.person.get_parent_family_handle_list() - if parent_handle_list: - parent_handle = parent_handle_list[0] - family = self.r_db.get_family_from_handle(parent_handle) - father_handle = family.get_father_handle() - mother_handle = family.get_mother_handle() - if mother_handle: - mother = self.r_db.get_person_from_handle(mother_handle) - else: - mother = None - if father_handle: - father = self.r_db.get_person_from_handle(father_handle) - else: - father = None - else: - family = None - father = None - mother = None - - with Html("div", id="pedigree", class_="subsection") as ped: - ped += Html("h4", self._('Pedigree'), inline=True) - with Html("ol", class_="pedigreegen") as pedol: - ped += pedol - if father and mother: - pedfa = Html("li") + self.pedigree_person(father) - pedol += pedfa - with Html("ol") as pedma: - pedfa += pedma - pedma += (Html("li", class_="spouse") + - self.pedigree_person(mother) + - children_ped(Html("ol")) - ) - elif father: - pedol += (Html("li") + self.pedigree_person(father) + - children_ped(Html("ol")) - ) - elif mother: - pedol += (Html("li") + self.pedigree_person(mother) + - children_ped(Html("ol")) - ) - else: - pedol += (Html("li") + children_ped(Html("ol"))) - return ped - - def display_ind_general(self): - """ - display an individual's general information... - """ - self.page_title = self.sort_name - thumbnail = self.disp_first_img_as_thumbnail( - self.person.get_media_list(), self.person) - section_title = Html("h3", html_escape(self.page_title), - inline=True) + ( - Html('sup') + ( - Html('small') + - self.get_citation_links( - self.person.get_citation_list()))) - - # begin summaryarea division - with Html("div", id='summaryarea') as summaryarea: - - # begin general details table - with Html("table", class_="infolist") as table: - summaryarea += table - - primary_name = self.person.get_primary_name() - all_names = [primary_name] + self.person.get_alternate_names() - # if the callname or the nickname is the same as the 'first - # name' (given name), then they are not displayed. - first_name = primary_name.get_first_name() - - # Names [and their sources] - for name in all_names: - pname = html_escape(_nd.display_name(name)) - pname += self.get_citation_links(name.get_citation_list()) - - # if we have just a firstname, then the name is preceeded - # by ", " which doesn't exactly look very nice printed on - # the web page - if pname[:2] == ', ': - pname = pname[2:] - if name != primary_name: - datetext = self.rlocale.get_date(name.date) - if datetext: - pname = datetext + ': ' + pname - - type_ = self._(name.get_type().xml_str()) - - trow = Html("tr") + ( - Html("td", type_, class_="ColumnAttribute", - inline=True) - ) - - tcell = Html("td", pname, class_="ColumnValue") - # display any notes associated with this name - notelist = name.get_note_list() - if len(notelist): - unordered = Html("ul") - - for notehandle in notelist: - note = self.r_db.get_note_from_handle(notehandle) - if note: - note_text = self.get_note_format(note, True) - - # attach note - unordered += note_text - tcell += unordered - trow += tcell - table += trow - - # display the callname associated with this name. - call_name = name.get_call_name() - if call_name and call_name != first_name: - trow = Html("tr") + ( - Html("td", _("Call Name"), class_="ColumnAttribute", - inline=True), - Html("td", call_name, class_="ColumnValue", - inline=True) - ) - table += trow - - # display the nickname associated with this name. Note that - # this no longer displays the Nickname attribute (if - # present), because the nickname attribute is deprecated in - # favour of the nick_name property of the name structure - # (see http://gramps.1791082.n4.nabble.com/Where-is- - # nickname-stored-tp4469779p4484272.html), and also because - # the attribute is (normally) displayed lower down the - # wNarrative Web report. - nick_name = name.get_nick_name() - if nick_name and nick_name != first_name: - trow = Html("tr") + ( - Html("td", self._("Nick Name"), - class_="ColumnAttribute", - inline=True), - Html("td", nick_name, class_="ColumnValue", - inline=True) - ) - table += trow - - # Gramps ID - person_gid = self.person.get_gramps_id() - if not self.noid and person_gid: - trow = Html("tr") + ( - Html("td", self._("Gramps ID"), - class_="ColumnAttribute", - inline=True), - Html("td", person_gid, class_="ColumnValue", - inline=True) - ) - table += trow - - # Gender - gender = self._(self.gender_map[self.person.gender]) - trow = Html("tr") + ( - Html("td", self._("Gender"), class_="ColumnAttribute", - inline=True), - Html("td", gender, class_="ColumnValue", inline=True) - ) - table += trow - - # Age At Death??? - birth_date = Date.EMPTY - birth_ref = self.person.get_birth_ref() - if birth_ref: - birth = self.r_db.get_event_from_handle(birth_ref.ref) - if birth: - birth_date = birth.get_date_object() - - if birth_date and birth_date is not Date.EMPTY: - alive = probably_alive(self.person, self.r_db, Today()) - - death_date = _find_death_date(self.r_db, self.person) - if not alive and death_date is not None: - nyears = death_date - birth_date - nyears = nyears.format(precision=3, - dlocale=self.rlocale) - trow = Html("tr") + ( - Html("td", self._("Age at Death"), - class_="ColumnAttribute", inline=True), - Html("td", nyears, - class_="ColumnValue", inline=True) - ) - table += trow - - # return all three pieces to its caller - # do NOT combine before returning - return thumbnail, section_title, summaryarea - - def display_ind_events(self, place_lat_long): - """ - will create the events table - - @param: place_lat_long -- For use in Family Map Pages. This will be None - if called from Family pages, which do not - create a Family Map - """ - event_ref_list = self.person.get_event_ref_list() - if not event_ref_list: - return None - - # begin events division and section title - with Html("div", id="events", class_="subsection") as section: - section += Html("h4", self._("Events"), inline=True) - - # begin events table - with Html("table", class_="infolist eventlist") as table: - section += table - - thead = Html("thead") - table += thead - - # attach event header row - thead += self.event_header_row() - - tbody = Html("tbody") - table += tbody - - for evt_ref in event_ref_list: - event = self.r_db.get_event_from_handle(evt_ref.ref) - if event: - - # display event row - tbody += self.display_event_row(event, evt_ref, - place_lat_long, - True, True, - EventRoleType.PRIMARY) - return section - - def display_parent(self, handle, title, rel): - """ - This will display a parent ... - - @param: handle -- The person handle - @param: title -- Is the title of the web page - @param: rel -- The relation - """ - tcell1 = Html("td", title, class_="ColumnAttribute", inline=True) - tcell2 = Html("td", class_="ColumnValue", close=False, inline=True) - - tcell2 += self.new_person_link(handle, uplink=True) - - if rel and rel != ChildRefType(ChildRefType.BIRTH): - tcell2 += ''.join([' '] *3 + ['(%s)']) % str(rel) - - person = self.r_db.get_person_from_handle(handle) - birth = death = "" - if person: - bd_event = get_birth_or_fallback(self.r_db, person) - if bd_event: - birth = self.rlocale.get_date(bd_event.get_date_object()) - dd_event = get_death_or_fallback(self.r_db, person) - if dd_event: - death = self.rlocale.get_date(dd_event.get_date_object()) - - tcell3 = Html("td", birth, class_="ColumnDate", - inline=False, close=False, indent=False) - - tcell4 = Html("td", death, class_="ColumnDate", - inline=True, close=False, indent=False) - - tcell2 += tcell3 - tcell2 += tcell4 - - # return table columns to its caller - return tcell1, tcell2 - - def get_reln_in_family(self, ind, family): - """ - Display the relation of the indiv in the family - - @param: ind -- The person to use - @param: family -- The family - """ - child_handle = ind.get_handle() - child_ref_list = family.get_child_ref_list() - for child_ref in child_ref_list: - if child_ref.ref == child_handle: - return (child_ref.get_father_relation(), - child_ref.get_mother_relation()) - return (None, None) - - def display_ind_parent_family(self, birthmother, birthfather, family, - table, - first=False): - """ - Display the individual parent family - - @param: birthmother -- The birth mother - @param: birthfather -- The birth father - @param: family -- The family - @param: table -- The html document to complete - @param: first -- Is this the first indiv ? - """ - if not first: - trow = Html("tr") + (Html("td", " ", colspan=3, - inline=True)) - table += trow - - # get the father - father_handle = family.get_father_handle() - if father_handle: - if father_handle == birthfather: - # The parent may not be birth father in ths family, because it - # may be a step family. However, it will be odd to display the - # parent as anything other than "Father" - reln = self._("Father") - else: - # Stepfather may not always be quite right (for example, it may - # actually be StepFather-in-law), but it is too expensive to - # calculate out the correct relationship using the Relationship - # Calculator - reln = self._("Stepfather") - trow = Html("tr") + (self.display_parent(father_handle, reln, None)) - table += trow - - # get the mother - mother_handle = family.get_mother_handle() - if mother_handle: - if mother_handle == birthmother: - reln = self._("Mother") - else: - reln = self._("Stepmother") - trow = Html("tr") + (self.display_parent(mother_handle, reln, None)) - table += trow - - for child_ref in family.get_child_ref_list(): - child_handle = child_ref.ref - child = self.r_db.get_person_from_handle(child_handle) - if child: - if child == self.person: - reln = "" - else: - try: - # We have a try except block here, because the two - # people MUST be siblings for the called Relationship - # routines to work. Depending on your definition of - # sibling, we cannot necessarily guarantee that. - sibling_type = self.rel_class.get_sibling_type( - self.r_db, self.person, child) - - reln = self.rel_class.get_sibling_relationship_string( - sibling_type, self.person.gender, child.gender) - # We have a problem here : reln is never in the choosen - # language but in the default language. - # Does get_sibling_relationship_string work ? - reln = reln[0].upper() + reln[1:] - except: - reln = self._("Not siblings") - - val1 = "    " - reln = val1 + reln - # Now output reln, child_link, (frel, mrel) - frel = child_ref.get_father_relation() - mrel = child_ref.get_mother_relation() - if frel != ChildRefType.BIRTH or mrel != ChildRefType.BIRTH: - frelmrel = "(%s, %s)" % (str(frel), str(mrel)) - else: - frelmrel = "" - trow = Html("tr") + ( - Html("td", reln, class_="ColumnAttribute", inline=True)) - - tcell = Html("td", val1, class_="ColumnValue", inline=True) - tcell += self.display_child_link(child_handle) - - birth = death = "" - bd_event = get_birth_or_fallback(self.r_db, child) - if bd_event: - birth = self.rlocale.get_date(bd_event.get_date_object()) - dd_event = get_death_or_fallback(self.r_db, child) - if dd_event: - death = self.rlocale.get_date(dd_event.get_date_object()) - - tcell2 = Html("td", birth, class_="ColumnDate", - inline=True) - - tcell3 = Html("td", death, class_="ColumnDate", - inline=True) - - trow += tcell - trow += tcell2 - trow += tcell3 - - tcell = Html("td", frelmrel, class_="ColumnValue", - inline=True) - trow += tcell - table += trow - - def display_step_families(self, parent_handle, - family, - all_family_handles, - birthmother, birthfather, - table): - """ - Display step families - - @param: parent_handle -- The family parent handle to display - @param: family -- The family - @param: all_family_handles -- All known family handles - @param: birthmother -- The birth mother - @param: birthfather -- The birth father - @param: table -- The html document to complete - """ - if parent_handle: - parent = self.r_db.get_person_from_handle(parent_handle) - for parent_family_handle in parent.get_family_handle_list(): - if parent_family_handle not in all_family_handles: - parent_family = self.r_db.get_family_from_handle( - parent_family_handle) - self.display_ind_parent_family(birthmother, birthfather, - parent_family, table) - all_family_handles.append(parent_family_handle) - - def display_ind_center_person(self): - """ - Display the person's relationship to the center person - """ - center_person = self.r_db.get_person_from_gramps_id( - self.report.options['pid']) - relationship = self.rel_class.get_one_relationship(self.r_db, - self.person, - center_person) - if relationship == "": # No relation to display - return - - # begin center_person division - section = "" - with Html("div", class_="subsection", id="parents") as section: - message = self._("Relation to the center person") - message += " (" - name_format = self.report.options['name_format'] - primary_name = center_person.get_primary_name() - name = Name(primary_name) - name.set_display_as(name_format) - message += _nd.display_name(name) - message += ") : " - message += relationship - section += Html("h4", message, inline=True) - return section - - def display_ind_parents(self): - """ - Display a person's parents - """ - parent_list = self.person.get_parent_family_handle_list() - if not parent_list: - return None - - # begin parents division - with Html("div", class_="subsection", id="parents") as section: - section += Html("h4", self._("Parents"), inline=True) - - # begin parents table - with Html("table", class_="infolist") as table: - section += table - - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - trow.extend( - Html("th", label, class_=colclass, inline=True) - for (label, colclass) in [ - (self._("Relation to main person"), "ColumnAttribute"), - (self._("Name"), "ColumnValue"), - (self._("Birth date"), "ColumnValue"), - (self._("Death date"), "ColumnValue"), - (self._("Relation within this family " - "(if not by birth)"), - "ColumnValue") - ] - ) - - tbody = Html("tbody") - - all_family_handles = list(parent_list) - (birthmother, birthfather) = self.rel_class.get_birth_parents( - self.r_db, self.person) - - first = True - for family_handle in parent_list: - family = self.r_db.get_family_from_handle(family_handle) - if family: - # Display this family - self.display_ind_parent_family(birthmother, - birthfather, - family, tbody, first) - first = False - - if self.report.options['showhalfsiblings']: - # Display all families in which the parents are - # involved. This displays half siblings and step - # siblings - self.display_step_families( - family.get_father_handle(), family, - all_family_handles, - birthmother, birthfather, tbody) - self.display_step_families( - family.get_mother_handle(), family, - all_family_handles, - birthmother, birthfather, tbody) - table += tbody - return section - - def pedigree_person(self, person): - """ - will produce a hyperlink for a pedigree person ... - - @param: person -- The person - """ - hyper = self.new_person_link(person.handle, person=person, uplink=True) - return hyper - - def pedigree_family(self): - """ - Returns a family pedigree - """ - ped = [] - for family_handle in self.person.get_family_handle_list(): - rel_family = self.r_db.get_family_from_handle(family_handle) - spouse_handle = utils.find_spouse(self.person, rel_family) - if spouse_handle: - spouse = self.r_db.get_person_from_handle(spouse_handle) - pedsp = (Html("li", class_="spouse") + - self.pedigree_person(spouse) - ) - else: - pedsp = (Html("li", class_="spouse")) - ped += [pedsp] - childlist = rel_family.get_child_ref_list() - if childlist: - with Html("ol") as childol: - pedsp += [childol] - for child_ref in childlist: - child = self.r_db.get_person_from_handle(child_ref.ref) - if child: - childol += (Html("li") + - self.pedigree_person(child) - ) - return ped - -################################################# -# -# creates the Repository List Page and Repository Pages -# -################################################# -class RepositoryPages(BasePage): - """ - This class is responsible for displaying information about the 'Repository' - database objects. It displays this information under the 'Individuals' - tab. It is told by the 'add_instances' call which 'Repository's to display, - and remembers the list of persons. A single call to 'display_pages' - displays both the Individual List (Index) page and all the Individual - pages. - - The base class 'BasePage' is initialised once for each page that is - displayed. - """ - def __init__(self, report): - """ - @param: report -- The instance of the main report class for this report - """ - BasePage.__init__(self, report, title="") - self.repos_dict = defaultdict(set) - - def display_pages(self, title): - """ - Generate and output the pages under the Repository tab, namely the - repository index and the individual repository pages. - - @param: title -- Is the title of the web page - """ - LOG.debug("obj_dict[Person]") - for item in self.report.obj_dict[Repository].items(): - LOG.debug(" %s", str(item)) - - # set progress bar pass for Repositories - with self.r_user.progress(_("Narrated Web Site Report"), - _('Creating repository pages'), - len(self.report.obj_dict[Repository]) + 1 - ) as step: - # Sort the repositories - repos_dict = {} - for repo_handle in self.report.obj_dict[Repository]: - repository = self.r_db.get_repository_from_handle(repo_handle) - key = repository.get_name() + str(repository.get_gramps_id()) - repos_dict[key] = (repository, repo_handle) - - keys = sorted(repos_dict, key=self.rlocale.sort_key) - - # RepositoryListPage Class - self.repositorylistpage(self.report, title, repos_dict, keys) - - for index, key in enumerate(keys): - (repo, handle) = repos_dict[key] - - step() - self.repositorypage(self.report, title, repo, handle) - - def repositorylistpage(self, report, title, repos_dict, keys): - """ - Create Index for repositories - - @param: report -- The instance of the main report class - for this report - @param: title -- Is the title of the web page - @param: repos_dict -- The dictionary for all repositories - @param: keys -- The keys used to access repositories - """ - BasePage.__init__(self, report, title) - #inc_repos = self.report.options["inc_repository"] - - output_file, sio = self.report.create_file("repositories") - repolistpage, head, body = self.write_header(_("Repositories")) - - ldatec = 0 - # begin RepositoryList division - with Html("div", class_="content", - id="RepositoryList") as repositorylist: - body += repositorylist - - msg = self._("This page contains an index of " - "all the repositories in the " - "database, sorted by their title. " - "Clicking on a repositories’s " - "title will take you to that repositories’s page.") - repositorylist += Html("p", msg, id="description") - - # begin repositories table and table head - with Html("table", class_="infolist primobjlist repolist") as table: - repositorylist += table - - thead = Html("thead") - table += thead - - trow = Html("tr") + ( - Html("th", " ", class_="ColumnRowLabel", inline=True), - Html("th", self._("Type"), class_="ColumnType", - inline=True), - Html("th", self._("Repository |Name"), class_="ColumnName", - inline=True) - ) - thead += trow - - # begin table body - tbody = Html("tbody") - table += tbody - - for index, key in enumerate(keys): - (repo, handle) = repos_dict[key] - - trow = Html("tr") - tbody += trow - - # index number - trow += Html("td", index + 1, class_="ColumnRowLabel", - inline=True) - - # repository type - rtype = self._(repo.type.xml_str()) - trow += Html("td", rtype, class_="ColumnType", inline=True) - - # repository name and hyperlink - if repo.get_name(): - trow += Html("td", - self.repository_link(handle, - repo.get_name(), - repo.get_gramps_id(), - self.uplink), - class_="ColumnName") - ldatec = repo.get_change_time() - else: - trow += Html("td", "[ untitled ]", class_="ColumnName") - - # add clearline for proper styling - # add footer section - footer = self.write_footer(ldatec) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(repolistpage, output_file, sio, ldatec) - - def repositorypage(self, report, title, repo, handle): - """ - Create one page for one repository. - - @param: report -- The instance of the main report class for this report - @param: title -- Is the title of the web page - @param: repo -- the repository to use - @param: handle -- the handle to use - """ - gid = repo.get_gramps_id() - BasePage.__init__(self, report, title, gid) - ldatec = repo.get_change_time() - - output_file, sio = self.report.create_file(handle, 'repo') - self.uplink = True - repositorypage, head, body = self.write_header(_('Repositories')) - - # begin RepositoryDetail division and page title - with Html("div", class_="content", - id="RepositoryDetail") as repositorydetail: - body += repositorydetail - - # repository name - repositorydetail += Html("h3", html_escape(repo.name), inline=True) - - # begin repository table - with Html("table", class_="infolist repolist") as table: - repositorydetail += table - - tbody = Html("tbody") - table += tbody - - if not self.noid and gid: - trow = Html("tr") + ( - Html("td", self._("Gramps ID"), - class_="ColumnAttribute", - inline=True), - Html("td", gid, class_="ColumnValue", inline=True) - ) - tbody += trow - - trow = Html("tr") + ( - Html("td", self._("Type"), class_="ColumnAttribute", - inline=True), - Html("td", self._(repo.get_type().xml_str()), - class_="ColumnValue", - inline=True) - ) - tbody += trow - - # repository: address(es)... - # repository addresses do NOT have Sources - repo_address = self.display_addr_list(repo.get_address_list(), - False) - if repo_address is not None: - repositorydetail += repo_address - - # repository: urllist - urllist = self.display_url_list(repo.get_url_list()) - if urllist is not None: - repositorydetail += urllist - - # reposity: notelist - notelist = self.display_note_list(repo.get_note_list()) - if notelist is not None: - repositorydetail += notelist - - # display Repository Referenced Sources... - ref_list = self.display_bkref_list(Repository, repo.get_handle()) - if ref_list is not None: - repositorydetail += ref_list - - # add clearline for proper styling - # add footer section - footer = self.write_footer(ldatec) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(repositorypage, output_file, sio, ldatec) - -class AddressBookListPage(BasePage): - """ - Create the index for addresses. - """ - def __init__(self, report, title, has_url_addr_res): - """ - @param: report -- The instance of the main report class - for this report - @param: title -- Is the title of the web page - @param: has_url_addr_res -- The url, address and residence to use - for the report - """ - BasePage.__init__(self, report, title) - - # Name the file, and create it - output_file, sio = self.report.create_file("addressbook") - - # Add xml, doctype, meta and stylesheets - addressbooklistpage, head, body = self.write_header(_("Address Book")) - - # begin AddressBookList division - with Html("div", class_="content", - id="AddressBookList") as addressbooklist: - body += addressbooklist - - # Address Book Page message - msg = _("This page contains an index of all the individuals in " - "the database, sorted by their surname, with one of the " - "following: Address, Residence, or Web Links. " - "Selecting the person’s name will take you " - "to their individual Address Book page.") - addressbooklist += Html("p", msg, id="description") - - # begin Address Book table - with Html("table", - class_="infolist primobjlist addressbook") as table: - addressbooklist += table - - thead = Html("thead") - table += thead - - trow = Html("tr") - thead += trow - - trow.extend( - Html("th", label, class_=colclass, inline=True) - for (label, colclass) in [ - [" ", "ColumnRowLabel"], - [_("Full Name"), "ColumnName"], - [_("Address"), "ColumnAddress"], - [_("Residence"), "ColumnResidence"], - [_("Web Links"), "ColumnWebLinks"] - ] - ) - - tbody = Html("tbody") - table += tbody - - index = 1 - for (sort_name, person_handle, - has_add, has_res, - has_url) in has_url_addr_res: - - address = None - residence = None - weblinks = None - - # has address but no residence event - if has_add and not has_res: - address = "X" - - # has residence, but no addresses - elif has_res and not has_add: - residence = "X" - - # has residence and addresses too - elif has_add and has_res: - address = "X" - residence = "X" - - # has Web Links - if has_url: - weblinks = "X" - - trow = Html("tr") - tbody += trow - - trow.extend( - Html("td", data or " ", class_=colclass, - inline=True) - for (colclass, data) in [ - ["ColumnRowLabel", index], - ["ColumnName", - self.addressbook_link(person_handle)], - ["ColumnAddress", address], - ["ColumnResidence", residence], - ["ColumnWebLinks", weblinks] - ] - ) - index += 1 - - # Add footer and clearline - footer = self.write_footer(None) - body += (FULLCLEAR, footer) - - # send the page out for processing - # and close the file - self.xhtml_writer(addressbooklistpage, output_file, sio, 0) - -class AddressBookPage(BasePage): - """ - Create one page for one Address - """ - def __init__(self, report, title, person_handle, has_add, has_res, has_url): - """ - @param: report -- The instance of the main report class - for this report - @param: title -- Is the title of the web page - @param: person_handle -- the url, address and residence to use - for the report - @param: has_add -- the address to use for the report - @param: has_res -- the residence to use for the report - @param: has_url -- the url to use for the report - """ - person = report.database.get_person_from_handle(person_handle) - BasePage.__init__(self, report, title, person.gramps_id) - self.bibli = Bibliography() - - self.uplink = True - - # set the file name and open file - output_file, sio = self.report.create_file(person_handle, "addr") - addressbookpage, head, body = self.write_header(_("Address Book")) - - # begin address book page division and section title - with Html("div", class_="content", - id="AddressBookDetail") as addressbookdetail: - body += addressbookdetail - - link = self.new_person_link(person_handle, uplink=True, - person=person) - addressbookdetail += Html("h3", link) - - # individual has an address - if has_add: - addressbookdetail += self.display_addr_list(has_add, None) - - # individual has a residence - if has_res: - addressbookdetail.extend( - self.dump_residence(res) - for res in has_res - ) - - # individual has a url - if has_url: - addressbookdetail += self.display_url_list(has_url) - - # add fullclear for proper styling - # and footer section to page - footer = self.write_footer(None) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(addressbookpage, output_file, sio, 0) - -class StatisticsPage(BasePage): - """ - Create one page for statistics - """ - def __init__(self, report, title, step): - """ - @param: report -- The instance of the main report class - for this report - @param: title -- Is the title of the web page - """ - import posixpath - BasePage.__init__(self, report, title) - self.bibli = Bibliography() - self.uplink = False - self.report = report - # set the file name and open file - output_file, sio = self.report.create_file("statistics") - addressbookpage, head, body = self.write_header(_("Statistics")) - (males, - females, - unknown) = self.get_gender(report.database.iter_person_handles()) - - mobjects = report.database.get_number_of_media() - npersons = report.database.get_number_of_people() - nfamilies = report.database.get_number_of_families() - nsurnames = len(set(report.database.surname_list)) - notfound = [] - total_media = 0 - mbytes = "0" - chars = 0 - for media in report.database.iter_media(): - total_media += 1 - fullname = media_path_full(report.database, media.get_path()) - try: - chars += posixpath.getsize(fullname) - length = len(str(chars)) - if chars <= 999999: - mbytes = _("less than 1") - else: - mbytes = str(chars)[:(length-6)] - except OSError: - notfound.append(media.get_path()) - - - with Html("div", class_="content", id='EventDetail') as section: - section += Html("h3", self._("Database overview"), inline=True) - body += section - with Html("div", class_="content", id='subsection narrative') as sec11: - sec11 += Html("h4", self._("Individuals"), inline=True) - body += sec11 - with Html("div", class_="content", id='subsection narrative') as sec1: - sec1 += Html("br", self._("Number of individuals") + self.COLON + - "%d" % npersons, inline=True) - sec1 += Html("br", self._("Males") + self.COLON + - "%d" % males, inline=True) - sec1 += Html("br", self._("Females") + self.COLON + - "%d" % females, inline=True) - sec1 += Html("br", self._("Individuals with unknown gender") + - self.COLON + "%d" % unknown, inline=True) - body += sec1 - with Html("div", class_="content", id='subsection narrative') as sec2: - sec2 += Html("h4", self._("Family Information"), inline=True) - sec2 += Html("br", self._("Number of families") + self.COLON + - "%d" % nfamilies, inline=True) - sec2 += Html("br", self._("Unique surnames") + self.COLON + - "%d" % nsurnames, inline=True) - body += sec2 - with Html("div", class_="content", id='subsection narrative') as sec3: - sec3 += Html("h4", self._("Media Objects"), inline=True) - sec3 += Html("br", - self._("Total number of media object references") + - self.COLON + "%d" % total_media, inline=True) - sec3 += Html("br", self._("Number of unique media objects") + - self.COLON + "%d" % mobjects, inline=True) - sec3 += Html("br", self._("Total size of media objects") + - self.COLON + - "%8s %s" % (mbytes, self._("Megabyte|MB")), - inline=True) - sec3 += Html("br", self._("Missing Media Objects") + - self.COLON + "%d" % len(notfound), inline=True) - body += sec3 - with Html("div", class_="content", id='subsection narrative') as sec4: - sec4 += Html("h4", self._("Miscellaneous"), inline=True) - sec4 += Html("br", self._("Number of events") + self.COLON + - "%d" % report.database.get_number_of_events(), - inline=True) - sec4 += Html("br", self._("Number of places") + self.COLON + - "%d" % report.database.get_number_of_places(), - inline=True) - nsources = report.database.get_number_of_sources() - sec4 += Html("br", self._("Number of sources") + - self.COLON + "%d" % nsources, - inline=True) - ncitations = report.database.get_number_of_citations() - sec4 += Html("br", self._("Number of citations") + - self.COLON + "%d" % ncitations, - inline=True) - nrepo = report.database.get_number_of_repositories() - sec4 += Html("br", self._("Number of repositories") + - self.COLON + "%d" % nrepo, - inline=True) - body += sec4 - - (males, - females, - unknown) = self.get_gender(self.report.bkref_dict[Person].keys()) - center_person = self.report.database.get_person_from_gramps_id( - self.report.options['pid']) - - origin = " :
    " + report.filter.get_name(self.rlocale) - with Html("div", class_="content", id='EventDetail') as section: - section += Html("h3", - self._("Narrative web content report for") + origin, - inline=True) - body += section - with Html("div", class_="content", id='subsection narrative') as sec5: - sec5 += Html("h4", self._("Individuals"), inline=True) - sec5 += Html("br", self._("Number of individuals") + self.COLON + - "%d" % len(self.report.bkref_dict[Person]), - inline=True) - sec5 += Html("br", self._("Males") + self.COLON + - "%d" % males, inline=True) - sec5 += Html("br", self._("Females") + self.COLON + - "%d" % females, inline=True) - sec5 += Html("br", self._("Individuals with unknown gender") + - self.COLON + "%d" % unknown, inline=True) - body += sec5 - with Html("div", class_="content", id='subsection narrative') as sec6: - sec6 += Html("h4", self._("Family Information"), inline=True) - sec6 += Html("br", self._("Number of families") + self.COLON + - "%d" % len(self.report.bkref_dict[Family]), - inline=True) - body += sec6 - with Html("div", class_="content", id='subsection narrative') as sec7: - sec7 += Html("h4", self._("Miscellaneous"), inline=True) - sec7 += Html("br", self._("Number of events") + self.COLON + - "%d" % len(self.report.bkref_dict[Event]), - inline=True) - sec7 += Html("br", self._("Number of places") + self.COLON + - "%d" % len(self.report.bkref_dict[Place]), - inline=True) - sec7 += Html("br", self._("Number of sources") + self.COLON + - "%d" % len(self.report.bkref_dict[Source]), - inline=True) - sec7 += Html("br", self._("Number of citations") + self.COLON + - "%d" % len(self.report.bkref_dict[Citation]), - inline=True) - sec7 += Html("br", self._("Number of repositories") + self.COLON + - "%d" % len(self.report.bkref_dict[Repository]), - inline=True) - body += sec7 - - # add fullclear for proper styling - # and footer section to page - footer = self.write_footer(None) - body += (FULLCLEAR, footer) - - # send page out for processing - # and close the file - self.xhtml_writer(addressbookpage, output_file, sio, 0) - - def get_gender(self, person_list): - """ - This function return the number of males, females and unknown gender - from a person list. - """ - males = 0 - females = 0 - unknown = 0 - for person_handle in person_list: - person = self.report.database.get_person_from_handle(person_handle) - gender = person.get_gender() - if gender == Person.MALE: - males += 1 - elif gender == Person.FEMALE: - females += 1 - else: - unknown += 1 - return (males, females, unknown) - class NavWebReport(Report): """ Create WebReport object that produces the report. @@ -8724,7 +508,8 @@ class NavWebReport(Report): self.obj_dict[Person][person_handle] = (person_fname, person_name, person.gramps_id) self.bkref_dict[Person][person_handle].add((bkref_class, - bkref_handle)) + bkref_handle, + "")) ############### Header section ############## for citation_handle in person.get_citation_list(): @@ -8741,9 +526,11 @@ class NavWebReport(Report): evt_ref_list = person.get_event_ref_list() if evt_ref_list: for evt_ref in evt_ref_list: + role = evt_ref.get_role().xml_str() event = self._db.get_event_from_handle(evt_ref.ref) if event: - self._add_event(evt_ref.ref, Person, person_handle) + self._add_event(evt_ref.ref, Person, person_handle, + role) place_handle = event.get_place_handle() if place_handle: self._add_place(place_handle, Person, @@ -8776,11 +563,12 @@ class NavWebReport(Report): family_evt_ref_list = family.get_event_ref_list() if family_evt_ref_list: for evt_ref in family_evt_ref_list: + role = evt_ref.get_role().xml_str() event = self._db.get_event_from_handle( evt_ref.ref) if event: self._add_event(evt_ref.ref, Person, - person_handle) + person_handle, "Primary") place_handle = event.get_place_handle() if place_handle: self._add_place(place_handle, Person, @@ -8865,7 +653,9 @@ class NavWebReport(Report): family_fname = "" self.obj_dict[Family][family_handle] = (family_fname, family_name, family.gramps_id) - self.bkref_dict[Family][family_handle].add((bkref_class, bkref_handle)) + self.bkref_dict[Family][family_handle].add((bkref_class, + bkref_handle, + "")) if self.inc_gallery: for media_ref in family.get_media_list(): @@ -8874,6 +664,7 @@ class NavWebReport(Report): ############### Events section ############## for evt_ref in family.get_event_ref_list(): + role = evt_ref.get_role().xml_str() event = self._db.get_event_from_handle(evt_ref.ref) place_handle = event.get_place_handle() if place_handle: @@ -8882,7 +673,7 @@ class NavWebReport(Report): if self.inc_events: # detail for family events are displayed on the events pages as # well as on this family page - self._add_event(evt_ref.ref, Family, family_handle) + self._add_event(evt_ref.ref, Family, family_handle, role) else: # There is no event page. Family events are displayed on the # family page, but the associated family event media may need to @@ -8929,8 +720,8 @@ class NavWebReport(Report): husband_name = self.get_person_name(husband) spouse_name = self.get_person_name(spouse) title_str = self._("Family of %(husband)s and %(spouse)s" - ) % {'husband' : husband_name, - 'spouse' : spouse_name} + ) % {'husband' : husband_name, + 'spouse' : spouse_name} elif husband: husband_name = self.get_person_name(husband) # Only the name of the husband is known @@ -8944,7 +735,7 @@ class NavWebReport(Report): return title_str - def _add_event(self, event_handle, bkref_class, bkref_handle): + def _add_event(self, event_handle, bkref_class, bkref_handle, role): """ Add event to the Event object list @@ -8983,7 +774,8 @@ class NavWebReport(Report): event_fname = "" self.obj_dict[Event][event_handle] = (event_fname, event_name, event.gramps_id) - self.bkref_dict[Event][event_handle].add((bkref_class, bkref_handle)) + self.bkref_dict[Event][event_handle].add((bkref_class, bkref_handle, + role)) ############### Attribute section ############## for attr in event.get_attribute_list(): @@ -9019,7 +811,9 @@ class NavWebReport(Report): False) + self.ext self.obj_dict[Place][place_handle] = (place_fname, place_name, place.gramps_id, event) - self.bkref_dict[Place][place_handle].add((bkref_class, bkref_handle)) + self.bkref_dict[Place][place_handle].add((bkref_class, bkref_handle, + "" # no role for a place + )) ############### Media section ############## if self.inc_gallery: @@ -9041,11 +835,16 @@ class NavWebReport(Report): """ source = self._db.get_source_from_handle(source_handle) source_name = source.get_title() + #if isinstance(source_name, bytes): + # print("source name :", source_name) source_fname = self.build_url_fname(source_handle, "src", False) + self.ext self.obj_dict[Source][source_handle] = (source_fname, source_name, source.gramps_id) - self.bkref_dict[Source][source_handle].add((bkref_class, bkref_handle)) + self.bkref_dict[Source][source_handle].add((bkref_class, + bkref_handle, + "" # no role + )) ############### Media section ############## if self.inc_gallery: @@ -9075,7 +874,9 @@ class NavWebReport(Report): self.obj_dict[Citation][citation_handle] = ("", citation_name, citation.gramps_id) self.bkref_dict[Citation][citation_handle].add((bkref_class, - bkref_handle)) + bkref_handle, + "" # no role + )) ############### Source section ############## self._add_source(source_handle, Citation, citation_handle) @@ -9110,7 +911,9 @@ class NavWebReport(Report): media_fname = "" self.obj_dict[Media][media_handle] = (media_fname, media_name, media.gramps_id) - self.bkref_dict[Media][media_handle].add((bkref_class, bkref_handle)) + self.bkref_dict[Media][media_handle].add((bkref_class, bkref_handle, + "" # no role for a media + )) ############### Attribute section ############## for attr in media.get_attribute_list(): @@ -9139,7 +942,9 @@ class NavWebReport(Report): self.obj_dict[Repository][repos_handle] = (repos_fname, repos_name, repos.gramps_id) self.bkref_dict[Repository][repos_handle].add((bkref_class, - bkref_handle)) + bkref_handle, + "" # no role + )) def copy_narrated_files(self): """ @@ -9260,8 +1065,11 @@ class NavWebReport(Report): # get death info: dod, pod = get_gendex_data(self._db, person.get_death_ref()) - filep.write( - '|'.join((url, surname, fullname, dob, pob, dod, pod)) + '|\n') + linew = '|'.join((url, surname, fullname, dob, pob, dod, pod)) + '|\n' + if self.archive: + filep.write(bytes(linew, "utf8")) + else: + filep.write(linew) def surname_pages(self, ind_list): """ @@ -9391,7 +1199,7 @@ class NavWebReport(Report): if self.target_uri not in subdirs: subdirs = [self.target_uri] + subdirs else: - if uplink == True: + if uplink is True: subdirs = ['..']*3 + subdirs # added for use in EventListPage @@ -9706,6 +1514,7 @@ class NavWebOptions(MenuReportOptions): self.__down_fname1 = None self.__navigation = None self.__target_cal_uri = None + self.__securesite = False db_options = name + ' ' + dbase.get_dbname() MenuReportOptions.__init__(self, db_options, dbase) @@ -9780,7 +1589,7 @@ class NavWebOptions(MenuReportOptions): Html Options for the Report. """ category_name = _("Html options") - addopt = partial( menu.add_option, category_name ) + addopt = partial(menu.add_option, category_name) ext = EnumeratedListOption(_("File extension"), ".html") for etype in _WEB_EXT: @@ -9854,7 +1663,7 @@ class NavWebOptions(MenuReportOptions): How to display names, datyes, ... """ category_name = _("Display") - addopt = partial( menu.add_option, category_name ) + addopt = partial(menu.add_option, category_name) stdoptions.add_name_format_option(menu, category_name) @@ -9864,7 +1673,7 @@ class NavWebOptions(MenuReportOptions): nogid = BooleanOption(_('Suppress Gramps ID'), False) nogid.set_help(_('Whether to include the Gramps ID of objects')) - addopt( "nogid", nogid ) + addopt("nogid", nogid) addopt = partial(menu.add_option, category_name) birthorder = BooleanOption( @@ -10136,7 +1945,7 @@ class NavWebOptions(MenuReportOptions): "to have for the Google Maps Family Map pages...")) addopt("googleopts", self.__googleopts) - self.__googlemapkey = StringOption(_("Google maps API key"),"") + self.__googlemapkey = StringOption(_("Google maps API key"), "") self.__googlemapkey.set_help(_("The API key used for the Google maps")) addopt("googlemapkey", self.__googlemapkey) @@ -10205,7 +2014,7 @@ class NavWebOptions(MenuReportOptions): """ Update the change of storage: archive or directory """ - if self.__archive.get_value() == True: + if self.__archive.get_value() is True: self.__target.set_extension(".tar.gz") self.__target.set_directory_entry(False) else: @@ -10318,95 +2127,6 @@ class NavWebOptions(MenuReportOptions): else: self.__googlemapkey.set_available(False) -# FIXME. Why do we need our own sorting? Why not use Sort? -def sort_people(dbase, handle_list, rlocale=glocale): - """ - will sort the database people by surname - """ - sname_sub = defaultdict(list) - sortnames = {} - - for person_handle in handle_list: - person = dbase.get_person_from_handle(person_handle) - primary_name = person.get_primary_name() - - if primary_name.group_as: - surname = primary_name.group_as - else: - surname = str( - dbase.get_name_group_mapping( - _nd.primary_surname(primary_name))) - - # Treat people who have no name with those whose name is just - # 'whitespace' - if surname is None or surname.isspace(): - surname = '' - sortnames[person_handle] = _nd.sort_string(primary_name) - sname_sub[surname].append(person_handle) - - sorted_lists = [] - temp_list = sorted(sname_sub, key=rlocale.sort_key) - - for name in temp_list: - slist = sorted(((sortnames[x], x) for x in sname_sub[name]), - key=lambda x: rlocale.sort_key(x[0])) - entries = [x[1] for x in slist] - sorted_lists.append((name, entries)) - - return sorted_lists - -def sort_event_types(dbase, event_types, event_handle_list, rlocale): - """ - sort a list of event types and their associated event handles - - @param: dbase -- report database - @param: event_types -- a dict of event types - @param: event_handle_list -- all event handles in this database - """ - event_dict = dict((evt_type, list()) for evt_type in event_types) - - for event_handle in event_handle_list: - - event = dbase.get_event_from_handle(event_handle) - event_type = rlocale.translation.sgettext(event.get_type().xml_str()) - - # add (gramps_id, date, handle) from this event - if event_type in event_dict: - sort_value = event.get_date_object().get_sort_value() - event_dict[event_type].append((sort_value, event_handle)) - - for tup_list in event_dict.values(): - tup_list.sort() - - # return a list of sorted tuples, one per event - retval = [(event_type, event_list) for (event_type, - event_list) in event_dict.items()] - retval.sort(key=lambda item: str(item[0])) - - return retval - -# Modified _get_regular_surname from WebCal.py to get prefix, first name, -# and suffix -def _get_short_name(gender, name): - """ Will get suffix for all people passed through it """ - - short_name = name.get_first_name() - suffix = name.get_suffix() - if suffix: - short_name = short_name + ", " + suffix - return short_name - -def __get_person_keyname(dbase, handle): - """ .... """ - - person = dbase.get_person_from_handle(handle) - return _nd.sort_string(person.get_primary_name()) - -def __get_place_keyname(dbase, handle): - """ ... """ - - return utils.place_name(dbase, handle) - # See : http://www.gramps-project.org/bugs/view.php?id = 4423 # Contraction data taken from CLDR 22.1. Only the default variant is considered. @@ -10508,310 +2228,3 @@ CONTRACTIONS_DICT = { # rather than primary. (3) There are plenty of other languages where a # difference that is primary in other languages is secondary, and those are not # specially handled. - -def first_letter(string, rlocale=glocale): - """ - Receives a string and returns the first letter - """ - if string is None or len(string) < 1: - return ' ' - - norm_unicode = normalize('NFKC', str(string)) - contractions = CONTRACTIONS_DICT.get(COLLATE_LANG) - if contractions == None: - contractions = CONTRACTIONS_DICT.get(COLLATE_LANG.split("_")[0]) - - if contractions is not None: - for contraction in contractions: - count = len(contraction[0][0]) - if (len(norm_unicode) >= count and - norm_unicode[:count] in contraction[0]): - return contraction[1] - - # no special case - return norm_unicode[0].upper() - -try: - import PyICU - PRIM_COLL = PyICU.Collator.createInstance(PyICU.Locale(COLLATE_LANG)) - PRIM_COLL.setStrength(PRIM_COLL.PRIMARY) - - def primary_difference(prev_key, new_key, rlocale=glocale): - """ - Try to use the PyICU collation. - """ - - return PRIM_COLL.compare(prev_key, new_key) != 0 - -except: - def primary_difference(prev_key, new_key, rlocale=glocale): - """ - The PyICU collation is not available. - - Returns true if there is a primary difference between the two parameters - See http://www.gramps-project.org/bugs/view.php?id=2933#c9317 if - letter[i]+'a' < letter[i+1]+'b' and letter[i+1]+'a' < letter[i]+'b' is - true then the letters should be grouped together - - The test characters here must not be any that are used in contractions. - """ - - return rlocale.sort_key(prev_key + "e") >= \ - rlocale.sort_key(new_key + "f") or \ - rlocale.sort_key(new_key + "e") >= \ - rlocale.sort_key(prev_key + "f") - -def get_first_letters(dbase, handle_list, key, rlocale=glocale): - """ - get the first letters of the handle_list - - @param: handle_list -- One of a handle list for either person or - place handles or an evt types list - @param: key -- Either a person, place, or event type - - The first letter (or letters if there is a contraction) are extracted from - all the objects in the handle list. There may be duplicates, and there may - be letters where there is only a secondary or tertiary difference, not a - primary difference. The list is sorted in collation order. For each group - with secondary or tertiary differences, the first in collation sequence is - retained. For example, assume the default collation sequence (DUCET) and - names Ã…nström and Apple. These will sort in the order shown. Ã… and A have a - secondary difference. If the first letter from these names was chosen then - the inex entry would be Ã…. This is not desirable. Instead, the initial - letters are extracted (Ã… and A). These are sorted, which gives A and Ã…. Then - the first of these is used for the index entry. - """ - index_list = [] - - for handle in handle_list: - if key == _KEYPERSON: - keyname = __get_person_keyname(dbase, handle) - - elif key == _KEYPLACE: - keyname = __get_place_keyname(dbase, handle) - - else: - if rlocale != glocale: - keyname = rlocale.translation.sgettext(handle) - else: - keyname = handle - ltr = first_letter(keyname) - - index_list.append(ltr) - - # Now remove letters where there is not a primary difference - index_list.sort(key=rlocale.sort_key) - first = True - prev_index = None - for key in index_list[:]: #iterate over a slice copy of the list - if first or primary_difference(prev_index, key, rlocale): - first = False - prev_index = key - else: - index_list.remove(key) - - # return menu set letters for alphabet_navigation - return index_list - -def get_index_letter(letter, index_list, rlocale=glocale): - """ - This finds the letter in the index_list that has no primary difference from - the letter provided. See the discussion in get_first_letters above. - Continuing the example, if letter is Ã… and index_list is A, then this would - return A. - """ - for index in index_list: - if not primary_difference(letter, index, rlocale): - return index - - LOG.warning("Initial letter '%s' not found in alphabetic navigation list", - letter) - LOG.debug("filtered sorted index list %s", index_list) - return letter - -def alphabet_navigation(index_list, rlocale=glocale): - """ - Will create the alphabet navigation bar for classes IndividualListPage, - SurnameListPage, PlaceListPage, and EventList - - @param: index_list -- a dictionary of either letters or words - """ - sorted_set = defaultdict(int) - - for menu_item in index_list: - sorted_set[menu_item] += 1 - - # remove the number of each occurance of each letter - sorted_alpha_index = sorted(sorted_set, key=rlocale.sort_key) - - # if no letters, return None to its callers - if not sorted_alpha_index: - return None - - num_ltrs = len(sorted_alpha_index) - num_of_cols = 26 - num_of_rows = ((num_ltrs // num_of_cols) + 1) - - # begin alphabet navigation division - with Html("div", id="alphanav") as alphabetnavigation: - - index = 0 - for row in range(num_of_rows): - unordered = Html("ul") - - cols = 0 - while cols <= num_of_cols and index < num_ltrs: - menu_item = sorted_alpha_index[index] - if menu_item == ' ': - menu_item = ' ' - # adding title to hyperlink menu for screen readers and - # braille writers - title_str = rlocale.translation.sgettext("Alphabet Menu: %s") % menu_item - hyper = Html("a", menu_item, title=title_str, - href="#%s" % menu_item) - unordered.extend(Html("li", hyper, inline=True)) - - index += 1 - cols += 1 - num_of_rows -= 1 - - alphabetnavigation += unordered - - return alphabetnavigation - -def _has_webpage_extension(url): - """ - determine if a filename has an extension or not... - - @param: url -- filename to be checked - """ - return any(url.endswith(ext) for ext in _WEB_EXT) - -def add_birthdate(dbase, ppl_handle_list, rlocale): - """ - This will sort a list of child handles in birth order - For each entry in the list, we'll have : - birth date - The transtated birth date for the configured locale - The transtated death date for the configured locale - The handle for the child - - @param: dbase -- The database to use - @param: ppl_handle_list -- the handle for the people - @param: rlocale -- the locale for date translation - """ - sortable_individuals = [] - for person_handle in ppl_handle_list: - birth_date = 0 # dummy value in case none is found - person = dbase.get_person_from_handle(person_handle) - if person: - birth_ref = person.get_birth_ref() - birth1 = "" - if birth_ref: - birth = dbase.get_event_from_handle(birth_ref.ref) - if birth: - birth1 = rlocale.get_date(birth.get_date_object()) - birth_date = birth.get_date_object().get_sort_value() - death_event = get_death_or_fallback(dbase, person) - if death_event: - death = rlocale.get_date(death_event.get_date_object()) - else: - death = "" - sortable_individuals.append((birth_date, birth1, death, person_handle)) - - # return a list of handles with the individual's birthdate attached - return sortable_individuals - -def _find_birth_date(dbase, individual): - """ - will look for a birth date within the person's events - - @param: dbase -- The database to use - @param: individual -- The individual for who we want to find the birth date - """ - date_out = None - birth_ref = individual.get_birth_ref() - if birth_ref: - birth = dbase.get_event_from_handle(birth_ref.ref) - if birth: - date_out = birth.get_date_object() - date_out.fallback = False - else: - person_evt_ref_list = individual.get_primary_event_ref_list() - if person_evt_ref_list: - for evt_ref in person_evt_ref_list: - event = dbase.get_event_from_handle(evt_ref.ref) - if event: - if event.get_type().is_birth_fallback(): - date_out = event.get_date_object() - date_out.fallback = True - LOG.debug("setting fallback to true for '%s'", event) - break - return date_out - -def _find_death_date(dbase, individual): - """ - will look for a death date within a person's events - - @param: dbase -- The database to use - @param: individual -- The individual for who we want to find the death date - """ - date_out = None - death_ref = individual.get_death_ref() - if death_ref: - death = dbase.get_event_from_handle(death_ref.ref) - if death: - date_out = death.get_date_object() - date_out.fallback = False - else: - person_evt_ref_list = individual.get_primary_event_ref_list() - if person_evt_ref_list: - for evt_ref in person_evt_ref_list: - event = dbase.get_event_from_handle(evt_ref.ref) - if event: - if event.get_type().is_death_fallback(): - date_out = event.get_date_object() - date_out.fallback = True - LOG.debug("setting fallback to true for '%s'", event) - break - return date_out - -def build_event_data_by_individuals(dbase, ppl_handle_list): - """ - creates a list of event handles and event types for this database - - @param: dbase -- The database to use - @param: ppl_handle_list -- the handle for the people - """ - event_handle_list = [] - event_types = [] - - for person_handle in ppl_handle_list: - person = dbase.get_person_from_handle(person_handle) - if person: - - evt_ref_list = person.get_event_ref_list() - if evt_ref_list: - for evt_ref in evt_ref_list: - event = dbase.get_event_from_handle(evt_ref.ref) - if event: - - event_types.append(str(event.get_type())) - event_handle_list.append(evt_ref.ref) - - person_family_handle_list = person.get_family_handle_list() - if person_family_handle_list: - for family_handle in person_family_handle_list: - family = dbase.get_family_from_handle(family_handle) - if family: - - family_evt_ref_list = family.get_event_ref_list() - if family_evt_ref_list: - for evt_ref in family_evt_ref_list: - event = dbase.get_event_from_handle(evt_ref.ref) - if event: - event_types.append(str(event.type)) - event_handle_list.append(evt_ref.ref) - - # return event_handle_list and event types to its caller - return event_handle_list, event_types diff --git a/gramps/plugins/webreport/person.py b/gramps/plugins/webreport/person.py new file mode 100644 index 000000000..363a54f33 --- /dev/null +++ b/gramps/plugins/webreport/person.py @@ -0,0 +1,1787 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + PersonPage - Person index page and individual `Person pages +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from collections import defaultdict +from operator import itemgetter +from decimal import Decimal, getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.lib import (ChildRefType, Date, Name, Person, EventRoleType) +from gramps.gen.lib.date import Today +from gramps.gen.plug.report import Bibliography +from gramps.gen.plug.report import utils +from gramps.gen.utils.alive import probably_alive +from gramps.gen.constfunc import win +from gramps.gen.display.name import displayer as _nd +from gramps.gen.utils.db import get_birth_or_fallback, get_death_or_fallback +from gramps.plugins.lib.libhtml import Html +from gramps.gen.utils.place import conv_lat_lon + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import (get_first_letters, _KEYPERSON, + alphabet_navigation, sort_people, + _NAME_STYLE_FIRST, first_letter, + get_index_letter, add_birthdate, + primary_difference, FULLCLEAR, + _find_birth_date, _find_death_date, + MARKER_PATH, OSM_MARKERS, + GOOGLE_MAPS, MARKERS, html_escape, + DROPMASTERS, FAMILYLINKS) + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +_WIDTH = 160 +_HEIGHT = 64 +_VGAP = 10 +_HGAP = 30 +_SHADOW = 5 +_XOFFSET = 5 + +################################################# +# +# creates the Individual List Page and IndividualPages +# +################################################# +class PersonPages(BasePage): + """ + This class is responsible for displaying information about the 'Person' + database objects. It displays this information under the 'Individuals' + tab. It is told by the 'add_instances' call which 'Person's to display, + and remembers the list of persons. A single call to 'display_pages' + displays both the Individual List (Index) page and all the Individual + pages. + + The base class 'BasePage' is initialised once for each page that is + displayed. + """ + def __init__(self, report): + """ + @param: report -- The instance of the main report class for this report + """ + BasePage.__init__(self, report, title="") + self.ind_dict = defaultdict(set) + self.mapservice = None + self.sort_name = None + self.googleopts = None + self.googlemapkey = None + self.birthorder = None + self.person = None + self.familymappages = None + self.rel_class = None + self.placemappages = None + self.name = None + + def display_pages(self, title): + """ + Generate and output the pages under the Individuals tab, namely the + individual index and the individual pages. + + @param: title -- Is the title of the web page + """ + LOG.debug("obj_dict[Person]") + for item in self.report.obj_dict[Person].items(): + LOG.debug(" %s", str(item)) + with self.r_user.progress(_("Narrated Web Site Report"), + _('Creating individual pages'), + len(self.report.obj_dict[Person]) + 1 + ) as step: + self.individuallistpage(self.report, title, + self.report.obj_dict[Person].keys()) + for person_handle in sorted(self.report.obj_dict[Person]): + step() + person = self.r_db.get_person_from_handle(person_handle) + self.individualpage(self.report, title, person) + +################################################# +# +# creates the Individual List Page +# +################################################# + def individuallistpage(self, report, title, ppl_handle_list): + """ + Creates an individual page + + @param: report -- The instance of the main report class + for this report + @param: title -- Is the title of the web page + @param: ppl_handle_list -- The list of people for whom we need + to create a page. + """ + BasePage.__init__(self, report, title) + prev_letter = " " + + # plugin variables for this module + showbirth = report.options['showbirth'] + showdeath = report.options['showdeath'] + showpartner = report.options['showpartner'] + showparents = report.options['showparents'] + + output_file, sio = self.report.create_file("individuals") + indlistpage, head, body = self.write_header(self._("Individuals")) + date = 0 + + # begin Individuals division + with Html("div", class_="content", id="Individuals") as individuallist: + body += individuallist + + # Individual List page message + msg = self._("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.") + individuallist += Html("p", msg, id="description") + + # add alphabet navigation + index_list = get_first_letters(self.r_db, ppl_handle_list, + _KEYPERSON, rlocale=self.rlocale) + alpha_nav = alphabet_navigation(index_list, self.rlocale) + if alpha_nav is not None: + individuallist += alpha_nav + + # begin table and table head + with Html("table", + class_="infolist primobjlist IndividualList") as table: + individuallist += table + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + # show surname and first name + trow += Html("th", self._("Surname"), class_="ColumnSurname", + inline=True) + trow += Html("th", self._("Given Name"), class_="ColumnName", + inline=True) + + if showbirth: + trow += Html("th", self._("Birth"), class_="ColumnDate", + inline=True) + + if showdeath: + trow += Html("th", self._("Death"), class_="ColumnDate", + inline=True) + + if showpartner: + trow += Html("th", self._("Partner"), + class_="ColumnPartner", + inline=True) + + if showparents: + trow += Html("th", self._("Parents"), + class_="ColumnParents", + inline=True) + + tbody = Html("tbody") + table += tbody + + ppl_handle_list = sort_people(self.r_db, ppl_handle_list, + self.rlocale) + first = True + for (surname, handle_list) in ppl_handle_list: + + if surname and not surname.isspace(): + letter = get_index_letter(first_letter(surname), index_list, + self.rlocale) + else: + letter = ' ' + surname = self._("") + + first_surname = True + for person_handle in sorted(handle_list, + key=self.sort_on_name_and_grampsid): + person = self.r_db.get_person_from_handle(person_handle) + if person.get_change_time() > date: + date = person.get_change_time() + + # surname column + trow = Html("tr") + tbody += trow + tcell = Html("td", class_="ColumnSurname", inline=True) + trow += tcell + + if first or primary_difference(letter, prev_letter, + self.rlocale): + first = False + first_surname = False + prev_letter = letter + trow.attr = 'class = "BeginSurname"' + ttle = self._("Surnames %(surname)s beginning " + "with letter %(letter)s" % + {'surname' : surname, + 'letter' : letter}) + tcell += Html( + "a", html_escape(surname), name=letter, + id_=letter, + title=ttle) + elif first_surname: + first_surname = False + tcell += Html("a", html_escape(surname), + title=self._("Surnames") + " " + surname) + else: + tcell += " " + + # firstname column + link = self.new_person_link(person_handle, person=person, + name_style=_NAME_STYLE_FIRST) + trow += Html("td", link, class_="ColumnName") + + # birth column + if showbirth: + tcell = Html("td", class_="ColumnBirth", inline=True) + trow += tcell + + birth_date = _find_birth_date(self.r_db, person) + if birth_date is not None: + if birth_date.fallback: + tcell += Html('em', + self.rlocale.get_date(birth_date), + inline=True) + else: + tcell += self.rlocale.get_date(birth_date) + else: + tcell += " " + + # death column + if showdeath: + tcell = Html("td", class_="ColumnDeath", inline=True) + trow += tcell + + death_date = _find_death_date(self.r_db, person) + if death_date is not None: + if death_date.fallback: + tcell += Html('em', + self.rlocale.get_date(death_date), + inline=True) + else: + tcell += self.rlocale.get_date(death_date) + else: + tcell += " " + + # partner column + if showpartner: + + family_list = person.get_family_handle_list() + first_family = True + #partner_name = None + tcell = () # pylint: disable=R0204 + if family_list: + for family_handle in family_list: + family = self.r_db.get_family_from_handle( + family_handle) + partner_handle = utils.find_spouse( + person, family) + if partner_handle: + if not first_family: + # have to do this to get the comma on + # the same line as the link + if isinstance(tcell[-1], Html): + # tcell is an instance of Html (or + # of a subclass thereof) + tcell[-1].inside += "," + else: + tcell = tcell[:-1] + ( + (tcell[-1] + ", "),) + # Have to manipulate as tuples so that + # subsequent people are not nested + # within the first link + tcell += ( + self.new_person_link(partner_handle),) + first_family = False + else: + tcell = " " + trow += Html("td", class_="ColumnPartner") + tcell + + # parents column + if showparents: + + parent_hdl_list = person.get_parent_family_handle_list() + if parent_hdl_list: + parent_handle = parent_hdl_list[0] + family = self.r_db.get_family_from_handle( + parent_handle) + father_handle = family.get_father_handle() + mother_handle = family.get_mother_handle() + if father_handle: + father = self.r_db.get_person_from_handle( + father_handle) + else: + father = None + if mother_handle: + mother = self.r_db.get_person_from_handle( + mother_handle) + else: + mother = None + if father: + father_name = self.get_name(father) + if mother: + mother_name = self.get_name(mother) + samerow = False + if mother and father: + tcell = (Html("span", father_name, + class_="father fatherNmother", + inline=True), + Html("span", mother_name, + class_="mother", inline=True)) + elif mother: + tcell = Html("span", mother_name, + class_="mother", inline=True) + elif father: + tcell = Html("span", father_name, + class_="father", inline=True) + else: + tcell = " " + samerow = True + else: + tcell = " " + samerow = True + trow += Html("td", class_="ColumnParents", + inline=samerow) + tcell + + # create clear line for proper styling + # create footer section + footer = self.write_footer(date) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(indlistpage, output_file, sio, date) + +################################################# +# +# creates an Individual Page +# +################################################# + gender_map = { + Person.MALE : _('male'), + Person.FEMALE : _('female'), + Person.UNKNOWN : _('unknown'), + } + + def individualpage(self, report, title, person): + """ + Creates an individual page + + @param: report -- The instance of the main report class for this report + @param: title -- Is the title of the web page + @param: person -- The person to use for this page. + """ + BasePage.__init__(self, report, title, person.get_gramps_id()) + place_lat_long = [] + + self.person = person + self.bibli = Bibliography() + self.sort_name = self.get_name(person) + self.name = self.get_name(person) + + date = self.person.get_change_time() + + # to be used in the Family Map Pages... + self.familymappages = self.report.options['familymappages'] + self.placemappages = self.report.options['placemappages'] + self.mapservice = self.report.options['mapservice'] + self.googleopts = self.report.options['googleopts'] + self.googlemapkey = self.report.options['googlemapkey'] + + # decide if we will sort the birth order of siblings... + self.birthorder = self.report.options['birthorder'] + + # get the Relationship Calculator so that we can determine + # bio, half, step- siblings for use in display_ind_parents() ... + self.rel_class = self.report.rel_class + + output_file, sio = self.report.create_file(person.get_handle(), "ppl") + self.uplink = True + indivdetpage, head, body = self.write_header(self.sort_name) + + # attach the ancestortree style sheet if ancestor + # graph is being created? + if self.report.options["ancestortree"]: + if self.usecms: + fname = "/".join([self.target_uri, "css", "ancestortree.css"]) + else: + fname = "/".join(["css", "ancestortree.css"]) + url = self.report.build_url_fname(fname, None, self.uplink) + head += Html("link", href=url, type="text/css", media="screen", + rel="stylesheet") + + # begin individualdetail division + with Html("div", class_="content", + id='IndividualDetail') as individualdetail: + body += individualdetail + + # display a person's general data + thumbnail, name, summary = self.display_ind_general() + if thumbnail is not None: + individualdetail += thumbnail + individualdetail += (name, summary) + + # display a person's events + sect2 = self.display_ind_events(place_lat_long) + if sect2 is not None: + individualdetail += sect2 + + # display relationship to the center person + sect3 = self.display_ind_center_person() + if sect3 is not None: + individualdetail += sect3 + + # display parents + sect4 = self.display_ind_parents() + if sect4 is not None: + individualdetail += sect4 + + # display relationships + relationships = self.display_relationships(self.person, + place_lat_long) + if relationships is not None: + individualdetail += relationships + + # display LDS ordinance + sect5 = self.display_lds_ordinance(self.person) + if sect5 is not None: + individualdetail += sect5 + + # display address(es) and show sources + sect6 = self.display_addr_list(self.person.get_address_list(), True) + if sect6 is not None: + individualdetail += sect6 + + photo_list = self.person.get_media_list() + media_list = photo_list[:] + + # if Family Pages are not being created, then include the Family + # Media objects? There is no reason to add these objects to the + # Individual Pages... + if not self.inc_families: + for handle in self.person.get_family_handle_list(): + family = self.r_db.get_family_from_handle(handle) + if family: + media_list += family.get_media_list() + for evt_ref in family.get_event_ref_list(): + event = self.r_db.get_event_from_handle(evt_ref.ref) + media_list += event.get_media_list() + + # if the Event Pages are not being created, then include the Event + # Media objects? There is no reason to add these objects to the + # Individual Pages... + if not self.inc_events: + for evt_ref in self.person.get_primary_event_ref_list(): + event = self.r_db.get_event_from_handle(evt_ref.ref) + if event: + media_list += event.get_media_list() + + # display additional images as gallery + sect7 = self.disp_add_img_as_gallery(media_list, person) + if sect7 is not None: + individualdetail += sect7 + + # display Narrative Notes + notelist = person.get_note_list() + sect8 = self.display_note_list(notelist) + if sect8 is not None: + individualdetail += sect8 + + # display attributes + attrlist = person.get_attribute_list() + if attrlist: + attrsection, attrtable = self.display_attribute_header() + self.display_attr_list(attrlist, attrtable) + individualdetail += attrsection + + # display web links + sect10 = self.display_url_list(self.person.get_url_list()) + if sect10 is not None: + individualdetail += sect10 + + # display associations + assocs = person.get_person_ref_list() + if assocs: + individualdetail += self.display_ind_associations(assocs) + + # for use in family map pages... + if len(place_lat_long) > 0: + if self.report.options["familymappages"]: + # save output_file, string_io and cur_fname + # before creating a new page + sof = output_file + sstring_io = sio + sfname = self.report.cur_fname + individualdetail += self.__display_family_map( + person, place_lat_long) + # restore output_file, string_io and cur_fname + # after creating a new page + output_file = sof + sio = sstring_io + self.report.cur_fname = sfname + + # display pedigree + sect13 = self.display_ind_pedigree() + if sect13 is not None: + individualdetail += sect13 + + # display ancestor tree + if report.options['ancestortree']: + sect14 = self.display_tree() + if sect14 is not None: + individualdetail += sect14 + + # display source references + sect14 = self.display_ind_sources(person) + if sect14 is not None: + individualdetail += sect14 + + # add clearline for proper styling + # create footer section + footer = self.write_footer(date) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(indivdetpage, output_file, sio, date) + + def __create_family_map(self, person, place_lat_long): + """ + creates individual family map page + + @param: person -- person from database + @param: place_lat_long -- for use in Family Map Pages + """ + if not place_lat_long: + return + + output_file, sio = self.report.create_file(person.get_handle(), "maps") + self.uplink = True + familymappage, head, body = self.write_header(self._("Family Map")) + + minx, maxx = Decimal("0.00000001"), Decimal("0.00000001") + miny, maxy = Decimal("0.00000001"), Decimal("0.00000001") + xwidth, yheight = [], [] + midx_, midy_, spanx, spany = [None]*4 + + number_markers = len(place_lat_long) + if number_markers > 1: + for (latitude, longitude, placetitle, handle, + date, etype) in place_lat_long: + xwidth.append(latitude) + yheight.append(longitude) + xwidth.sort() + yheight.sort() + + minx = xwidth[0] if xwidth[0] else minx + maxx = xwidth[-1] if xwidth[-1] else maxx + minx, maxx = Decimal(minx), Decimal(maxx) + midx_ = str(Decimal((minx + maxx) /2)) + + miny = yheight[0] if yheight[0] else miny + maxy = yheight[-1] if yheight[-1] else maxy + miny, maxy = Decimal(miny), Decimal(maxy) + midy_ = str(Decimal((miny + maxy) /2)) + + midx_, midy_ = conv_lat_lon(midx_, midy_, "D.D8") + + # get the integer span of latitude and longitude + spanx = int(maxx - minx) + spany = int(maxy - miny) + + # set zoom level based on span of Longitude? + tinyset = [value for value in (-3, -2, -1, 0, 1, 2, 3)] + smallset = [value for value in (-4, -5, -6, -7, 4, 5, 6, 7)] + middleset = [value for value in (-8, -9, -10, -11, 8, 9, 10, 11)] + largeset = [value for value in (-11, -12, -13, -14, -15, -16, + -17, 11, 12, 13, 14, 15, 16, 17)] + + if spany in tinyset or spany in smallset: + zoomlevel = 6 + elif spany in middleset: + zoomlevel = 5 + elif spany in largeset: + zoomlevel = 4 + else: + zoomlevel = 3 + + # 0 = latitude, 1 = longitude, 2 = place title, + # 3 = handle, and 4 = date, 5 = event type... + # being sorted by date, latitude, and longitude... + place_lat_long = sorted(place_lat_long, key=itemgetter(4, 0, 1)) + + # for all plugins + # if family_detail_page + # if active + # call_(report, up, head) + + # add narrative-maps style sheet + if self.usecms: + fname = "/".join([self.target_uri, "css", "narrative-maps.css"]) + else: + fname = "/".join(["css", "narrative-maps.css"]) + url = self.report.build_url_fname(fname, None, self.uplink) + head += Html("link", href=url, type="text/css", media="screen", + rel="stylesheet") + + # add MapService specific javascript code + if self.mapservice == "Google": + src_js = GOOGLE_MAPS + "api/js?sensor=false" + if self.googlemapkey: + src_js += "&key=" + self.googlemapkey + head += Html("script", type="text/javascript", + src=src_js, inline=True) + else: + url = self.secure_mode + url += ("maxcdn.bootstrapcdn.com/bootstrap/3.3.7/" + "css/bootstrap.min.css") + head += Html("link", href=url, type="text/javascript", + rel="stylesheet") + src_js = self.secure_mode + src_js += "ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" + head += Html("script", type="text/javascript", + src=src_js, inline=True) + src_js = self.secure_mode + src_js += "openlayers.org/en/v3.17.1/build/ol.js" + head += Html("script", type="text/javascript", + src=src_js, inline=True) + url = self.secure_mode + url += "openlayers.org/en/v3.17.1/css/ol.css" + head += Html("link", href=url, type="text/javascript", + rel="stylesheet") + src_js = self.secure_mode + src_js += ("maxcdn.bootstrapcdn.com/bootstrap/3.3.7/" + "js/bootstrap.min.js") + head += Html("script", type="text/javascript", + src=src_js, inline=True) + + if number_markers > 0: + tracelife = "[" + seq_ = 1 + + for index in range(0, (number_markers - 1)): + (latitude, longitude, placetitle, handle, date, + etype) = place_lat_long[index] + + # are we using Google? + if self.mapservice == "Google": + + # are we creating Family Links? + if self.googleopts == "FamilyLinks": + tracelife += """ + new google.maps.LatLng(%s, %s),""" % (latitude, longitude) + + # are we creating Drop Markers or Markers? + elif self.googleopts in ["Drop", "Markers"]: + tracelife += """ + ['%s', %s, %s, %d],""" % (placetitle.replace("'", "\\'"), latitude, + longitude, seq_) + + # are we using OpenStreetMap? + else: + tracelife += """ + [%f, %f, \'%s\'],""" % (float(longitude), float(latitude), + placetitle.replace("'", "\\'")) + + seq_ += 1 + # FIXME: The last element in the place_lat_long list is treated + # specially, and the code above is apparently repeated so as to + # avoid a comma at the end, and get the right closing. This is very + # ugly. + (latitude, longitude, placetitle, handle, date, + etype) = place_lat_long[-1] + + # are we using Google? + if self.mapservice == "Google": + + # are we creating Family Links? + if self.googleopts == "FamilyLinks": + tracelife += """ + new google.maps.LatLng(%s, %s) + ];""" % (latitude, longitude) + + # are we creating Drop Markers or Markers? + elif self.googleopts in ["Drop", "Markers"]: + tracelife += """ + ['%s', %s, %s, %d] + ];""" % (placetitle.replace("'", "\\'"), latitude, longitude, seq_) + + # are we using OpenStreetMap? + elif self.mapservice == "OpenStreetMap": + tracelife += """ + [%f, %f, \'%s\'] + ];""" % (float(longitude), float(latitude), placetitle.replace("'", "\\'")) + + # begin MapDetail division... + with Html("div", class_="content", id="FamilyMapDetail") as mapdetail: + body += mapdetail + + # add page title + mapdetail += Html("h3", + html_escape(self._("Tracking %s") + % self.get_name(person)), + inline=True) + + # page description + msg = self._("This map page represents that person " + "and any descendants with all of their event/ places. " + "If you place your mouse over " + "the marker it will display the place name. " + "The markers and the Reference " + "list are sorted in date order (if any?). " + "Clicking on a place’s " + "name in the Reference section will take you " + "to that place’s page.") + mapdetail += Html("p", msg, id="description") + + # this is the style element where the Map is held in the CSS... + with Html("div", id="map_canvas") as canvas: + mapdetail += canvas + + # begin javascript inline code... + with Html("script", deter="deter", + style='width =100%; height =100%;', + type="text/javascript", indent=False) as jsc: + head += jsc + + # Link to Gramps marker + fname = "/".join(['images', 'marker.png']) + marker_path = self.report.build_url_image("marker.png", + "images", + self.uplink) + + jsc += MARKER_PATH % marker_path + # are we using Google? + if self.mapservice == "Google": + + # are we creating Family Links? + if self.googleopts == "FamilyLinks": + if midy_ is None: + jsc += FAMILYLINKS % (tracelife, latitude, + longitude, int(10)) + else: + jsc += FAMILYLINKS % (tracelife, midx_, midy_, + zoomlevel) + + # are we creating Drop Markers? + elif self.googleopts == "Drop": + if midy_ is None: + jsc += DROPMASTERS % (tracelife, latitude, + longitude, int(10)) + else: + jsc += DROPMASTERS % (tracelife, midx_, midy_, + zoomlevel) + + # we are creating Markers only... + else: + if midy_ is None: + jsc += MARKERS % (tracelife, latitude, + longitude, int(10)) + else: + jsc += MARKERS % (tracelife, midx_, midy_, + zoomlevel) + + # we are using OpenStreetMap... + else: + if midy_ is None: + jsc += OSM_MARKERS % (tracelife, + longitude, + latitude, 10) + else: + jsc += OSM_MARKERS % (tracelife, midy_, midx_, + zoomlevel) + + # if Google and Drop Markers are selected, + # then add "Drop Markers" button? + if self.mapservice == "Google" and self.googleopts == "Drop": + mapdetail += Html("button", _("Drop Markers"), + id="drop", onclick="drop()", inline=True) + + # add div for popups. + with Html("div", id="popup", inline=True) as popup: + mapdetail += popup + + # begin place reference section and its table... + with Html("div", class_="subsection", id="references") as section: + mapdetail += section + section += Html("h4", self._("References"), inline=True) + + with Html("table", class_="infolist") as table: + section += table + + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + trow.extend( + Html("th", label, class_=colclass, inline=True) + for (label, colclass) in [ + (_("Date"), "ColumnDate"), + (_("Place Title"), "ColumnPlace"), + (_("Event Type"), "ColumnType") + ] + ) + + tbody = Html("tbody") + table += tbody + + for (latitude, longitude, placetitle, handle, date, + etype) in place_lat_long: + trow = Html("tr") + tbody += trow + + trow.extend( + Html("td", data, class_=colclass, inline=True) + for data, colclass in [ + (date, "ColumnDate"), + (self.place_link(handle, placetitle, + uplink=True), + "ColumnPlace"), + (str(etype), "ColumnType") + ] + ) + + # add body id for this page... + body.attr = 'id ="FamilyMap" onload ="initialize()"' + + # add clearline for proper styling + # add footer section + footer = self.write_footer(None) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(familymappage, output_file, sio, 0) + + def __display_family_map(self, person, place_lat_long): + """ + Create the family map link + + @param: person -- The person to set in the box + @param: place_lat_long -- The center of the box + """ + # create family map page + self.__create_family_map(person, place_lat_long) + + # begin family map division plus section title + with Html("div", class_="subsection", id="familymap") as familymap: + familymap += Html("h4", self._("Family Map"), inline=True) + + # add family map link + person_handle = person.get_handle() + url = self.report.build_url_fname_html(person_handle, "maps", True) + familymap += self.family_map_link(person_handle, url) + + # return family map link to its caller + return familymap + + def draw_box(self, center, col, person): + """ + Draw the box around the AncestorTree Individual name box... + + @param: center -- The center of the box + @param: col -- The generation number + @param: person -- The person to set in the box + """ + top = center - _HEIGHT/2 + xoff = _XOFFSET+col*(_WIDTH+_HGAP) + sex = person.gender + if sex == Person.MALE: + divclass = "male" + elif sex == Person.FEMALE: + divclass = "female" + else: + divclass = "unknown" + + boxbg = Html("div", class_="boxbg %s AncCol%s" % (divclass, col), + style="top: %dpx; left: %dpx;" % (top, xoff+1) + ) + + person_name = self.get_name(person) + # This does not use [new_]person_link because the requirements are + # unique + result = self.report.obj_dict.get(Person).get(person.handle) + if result is None or result[0] == "": + # The person is not included in the webreport or there is no link + # to them + boxbg += Html("span", person_name, class_="unlinked", inline=True) + else: + thumbnail_url = None + if self.create_media and col < 5: + photolist = person.get_media_list() + if photolist: + photo_handle = photolist[0].get_reference_handle() + photo = self.r_db.get_media_from_handle(photo_handle) + mime_type = photo.get_mime_type() + if mime_type: + region = self.media_ref_region_to_object(photo_handle, + person) + if region: + # make a thumbnail of this region + newpath = self.copy_thumbnail( + photo_handle, photo, region) + # TODO. Check if build_url_fname can be used. + newpath = "/".join(['..']*3 + [newpath]) + if win(): + newpath = newpath.replace('\\', "/") + thumbnail_url = newpath + else: + (photo_url, + thumbnail_url) = self.report.prepare_copy_media( + photo) + thumbnail_url = "/".join(['..']*3 + [thumbnail_url]) + if win(): + thumbnail_url = thumbnail_url.replace('\\', "/") + url = self.report.build_url_fname_html(person.handle, "ppl", True) + birth = death = "" + bd_event = get_birth_or_fallback(self.r_db, person) + if bd_event: + birth = self.rlocale.get_date(bd_event.get_date_object()) + dd_event = get_death_or_fallback(self.r_db, person) + if dd_event: + death = self.rlocale.get_date(dd_event.get_date_object()) + if death == "": + death = "..." + value = person_name + "
    *", birth, "
    +", death + if thumbnail_url is None: + boxbg += Html("a", href=url, class_="noThumb") + value + else: + thumb = Html("span", class_="thumbnail") + ( + Html("img", src=thumbnail_url, alt="Image: " + person_name)) + boxbg += Html("a", href=url) + thumb + value + shadow = Html( + "div", class_="shadow", inline=True, + style="top: %dpx; left: %dpx;" % (top + _SHADOW, xoff + _SHADOW)) + + return [boxbg, shadow] + + def extend_line(self, coord_y0, coord_x0): + """ + Draw and extended line + + @param: coord_y0 -- The starting point + @param: coord_x0 -- The end of the line + """ + style = "top: %dpx; left: %dpx; width: %dpx" + ext_bv = Html("div", class_="bvline", inline=True, + style=style % (coord_y0, coord_x0, _HGAP/2) + ) + ext_gv = Html("div", class_="gvline", inline=True, + style=style % (coord_y0+_SHADOW, + coord_x0, _HGAP/2+_SHADOW) + ) + return [ext_bv, ext_gv] + + def connect_line(self, coord_y0, coord_y1, col): + """ + We need to draw a line between to points + + @param: coord_y0 -- The starting point + @param: coord_y1 -- The end of the line + @param: col -- The generation number + """ + coord_y = min(coord_y0, coord_y1) + stylew = "top: %dpx; left: %dpx; width: %dpx;" + styleh = "top: %dpx; left: %dpx; height: %dpx;" + coord_x0 = _XOFFSET + col * _WIDTH + (col-1)*_HGAP + _HGAP/2 + cnct_bv = Html("div", class_="bvline", inline=True, + style=stylew % (coord_y1, coord_x0, _HGAP/2)) + cnct_gv = Html("div", class_="gvline", inline=True, + style=stylew % (coord_y1+_SHADOW, + coord_x0+_SHADOW, + _HGAP/2+_SHADOW)) + cnct_bh = Html("div", class_="bhline", inline=True, + style=styleh % (coord_y, coord_x0, + abs(coord_y0-coord_y1))) + cnct_gh = Html("div", class_="gvline", inline=True, + style=styleh % (coord_y+_SHADOW, + coord_x0+_SHADOW, + abs(coord_y0-coord_y1))) + return [cnct_bv, cnct_gv, cnct_bh, cnct_gh] + + def draw_connected_box(self, center1, center2, col, handle): + """ + Draws the connected box for Ancestor Tree on the Individual Page + + @param: center1 -- The first box to connect + @param: center2 -- The destination box to draw + @param: col -- The generation number + @param: handle -- The handle of the person to set in the new box + """ + box = [] + if not handle: + return box + person = self.r_db.get_person_from_handle(handle) + box = self.draw_box(center2, col, person) + box += self.connect_line(center1, center2, col) + return box + + def display_tree(self): + """ + Display the Ancestor Tree + """ + tree = [] + if not self.person.get_main_parents_family_handle(): + return None + + generations = self.report.options['graphgens'] + max_in_col = 1 << (generations-1) + max_size = _HEIGHT*max_in_col + _VGAP*(max_in_col+1) + center = int(max_size/2) + + with Html("div", id="tree", class_="subsection") as tree: + tree += Html("h4", self._('Ancestors'), inline=True) + with Html("div", id="treeContainer", + style="width:%dpx; height:%dpx;" % ( + _XOFFSET+(generations)*_WIDTH+(generations-1)*_HGAP, + max_size) + ) as container: + tree += container + container += self.draw_tree(1, generations, max_size, + 0, center, self.person.handle) + return tree + + def draw_tree(self, gen_nr, maxgen, max_size, old_center, + new_center, person_handle): + """ + Draws the Ancestor Tree + + @param: gen_nr -- The generation number to draw + @param: maxgen -- The maximum number of generations to draw + @param: max_size -- The maximum size of the drawing area + @param: old_center -- The position of the old box + @param: new_center -- The position of the new box + @param: person_handle -- The handle of the person to draw + """ + tree = [] + if gen_nr > maxgen: + return tree + gen_offset = int(max_size / pow(2, gen_nr+1)) + if person_handle: + person = self.r_db.get_person_from_handle(person_handle) + else: + person = None + if not person: + return tree + + if gen_nr == 1: + tree = self.draw_box(new_center, 0, person) + else: + tree = self.draw_connected_box(old_center, new_center, + gen_nr-1, person_handle) + + if gen_nr == maxgen: + return tree + + family_handle = person.get_main_parents_family_handle() + if family_handle: + line_offset = _XOFFSET + gen_nr*_WIDTH + (gen_nr-1)*_HGAP + tree += self.extend_line(new_center, line_offset) + + family = self.r_db.get_family_from_handle(family_handle) + + f_center = new_center-gen_offset + f_handle = family.get_father_handle() + tree += self.draw_tree(gen_nr+1, maxgen, max_size, + new_center, f_center, f_handle) + + m_center = new_center+gen_offset + m_handle = family.get_mother_handle() + tree += self.draw_tree(gen_nr+1, maxgen, max_size, + new_center, m_center, m_handle) + return tree + + def display_ind_associations(self, assoclist): + """ + Display an individual's associations + + @param: assoclist -- The list of persons for association + """ + # begin Associations division + with Html("div", class_="subsection", id="Associations") as section: + section += Html("h4", self._('Associations'), inline=True) + + with Html("table", class_="infolist assoclist") as table: + section += table + + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + assoc_row = [ + (self._("Person"), 'Person'), + (self._('Relationship'), 'Relationship'), + (self._("Notes"), 'Notes'), + (self._("Sources"), 'Sources'), + ] + + trow.extend( + Html("th", label, class_="Column" + colclass, inline=True) + for (label, colclass) in assoc_row) + + tbody = Html("tbody") + table += tbody + + for person_ref in assoclist: + trow = Html("tr") + tbody += trow + + person_lnk = self.new_person_link(person_ref.ref, + uplink=True) + + index = 0 + for data in [ + person_lnk, + person_ref.get_relation(), + self.dump_notes(person_ref.get_note_list()), + self.get_citation_links( + person_ref.get_citation_list()), + ]: + + # get colclass from assoc_row + colclass = assoc_row[index][1] + + trow += Html("td", data, class_="Column" + colclass, + inline=True) + index += 1 + + # return section to its callers + return section + + def display_ind_pedigree(self): + """ + Display an individual's pedigree + """ + birthorder = self.report.options["birthorder"] + + # Define helper functions + def children_ped(ol_html): + """ + Create a children list + + @param: ol_html -- The html element to complete + """ + if family: + childlist = family.get_child_ref_list() + + childlist = [child_ref.ref for child_ref in childlist] + children = add_birthdate(self.r_db, childlist, self.rlocale) + + if birthorder: + children = sorted(children) + + for birthdate, birth, death, handle in children: + if handle == self.person.get_handle(): + child_ped(ol_html) + elif handle: + child = self.r_db.get_person_from_handle(handle) + if child: + ol_html += Html("li") + self.pedigree_person(child) + else: + child_ped(ol_html) + return ol_html + + def child_ped(ol_html): + """ + Create a child element list + + @param: ol_html -- The html element to complete + """ + with Html("li", self.name, class_="thisperson") as pedfam: + family = self.pedigree_family() + if family: + pedfam += Html("ol", class_="spouselist") + family + return ol_html + pedfam + # End of helper functions + + parent_handle_list = self.person.get_parent_family_handle_list() + if parent_handle_list: + parent_handle = parent_handle_list[0] + family = self.r_db.get_family_from_handle(parent_handle) + father_handle = family.get_father_handle() + mother_handle = family.get_mother_handle() + if mother_handle: + mother = self.r_db.get_person_from_handle(mother_handle) + else: + mother = None + if father_handle: + father = self.r_db.get_person_from_handle(father_handle) + else: + father = None + else: + family = None + father = None + mother = None + + with Html("div", id="pedigree", class_="subsection") as ped: + ped += Html("h4", self._('Pedigree'), inline=True) + with Html("ol", class_="pedigreegen") as pedol: + ped += pedol + if father and mother: + pedfa = Html("li") + self.pedigree_person(father) + pedol += pedfa + with Html("ol") as pedma: + pedfa += pedma + pedma += (Html("li", class_="spouse") + + self.pedigree_person(mother) + + children_ped(Html("ol")) + ) + elif father: + pedol += (Html("li") + self.pedigree_person(father) + + children_ped(Html("ol")) + ) + elif mother: + pedol += (Html("li") + self.pedigree_person(mother) + + children_ped(Html("ol")) + ) + else: + pedol += (Html("li") + children_ped(Html("ol"))) + return ped + + def display_ind_general(self): + """ + display an individual's general information... + """ + self.page_title = self.sort_name + thumbnail = self.disp_first_img_as_thumbnail( + self.person.get_media_list(), self.person) + section_title = Html("h3", html_escape(self.page_title), + inline=True) + ( + Html('sup') + ( + Html('small') + + self.get_citation_links( + self.person.get_citation_list()))) + + # begin summaryarea division + with Html("div", id='summaryarea') as summaryarea: + + # begin general details table + with Html("table", class_="infolist") as table: + summaryarea += table + + primary_name = self.person.get_primary_name() + all_names = [primary_name] + self.person.get_alternate_names() + # if the callname or the nickname is the same as the 'first + # name' (given name), then they are not displayed. + first_name = primary_name.get_first_name() + + # Names [and their sources] + for name in all_names: + pname = html_escape(_nd.display_name(name)) + pname += self.get_citation_links(name.get_citation_list()) + + # if we have just a firstname, then the name is preceeded + # by ", " which doesn't exactly look very nice printed on + # the web page + if pname[:2] == ', ': + pname = pname[2:] + if name != primary_name: + datetext = self.rlocale.get_date(name.date) + if datetext: + pname = datetext + ': ' + pname + + type_ = self._(name.get_type().xml_str()) + + trow = Html("tr") + ( + Html("td", type_, class_="ColumnAttribute", + inline=True) + ) + + tcell = Html("td", pname, class_="ColumnValue") + # display any notes associated with this name + notelist = name.get_note_list() + if len(notelist): + unordered = Html("ul") + + for notehandle in notelist: + note = self.r_db.get_note_from_handle(notehandle) + if note: + note_text = self.get_note_format(note, True) + + # attach note + unordered += note_text + tcell += unordered + trow += tcell + table += trow + + # display the callname associated with this name. + call_name = name.get_call_name() + if call_name and call_name != first_name: + trow = Html("tr") + ( + Html("td", _("Call Name"), class_="ColumnAttribute", + inline=True), + Html("td", call_name, class_="ColumnValue", + inline=True) + ) + table += trow + + # display the nickname associated with this name. Note that + # this no longer displays the Nickname attribute (if + # present), because the nickname attribute is deprecated in + # favour of the nick_name property of the name structure + # (see http://gramps.1791082.n4.nabble.com/Where-is- + # nickname-stored-tp4469779p4484272.html), and also because + # the attribute is (normally) displayed lower down the + # wNarrative Web report. + nick_name = name.get_nick_name() + if nick_name and nick_name != first_name: + trow = Html("tr") + ( + Html("td", self._("Nick Name"), + class_="ColumnAttribute", + inline=True), + Html("td", nick_name, class_="ColumnValue", + inline=True) + ) + table += trow + + # Gramps ID + person_gid = self.person.get_gramps_id() + if not self.noid and person_gid: + trow = Html("tr") + ( + Html("td", self._("Gramps ID"), + class_="ColumnAttribute", + inline=True), + Html("td", person_gid, class_="ColumnValue", + inline=True) + ) + table += trow + + # Gender + gender = self._(self.gender_map[self.person.gender]) + trow = Html("tr") + ( + Html("td", self._("Gender"), class_="ColumnAttribute", + inline=True), + Html("td", gender, class_="ColumnValue", inline=True) + ) + table += trow + + # Age At Death??? + birth_date = Date.EMPTY + birth_ref = self.person.get_birth_ref() + if birth_ref: + birth = self.r_db.get_event_from_handle(birth_ref.ref) + if birth: + birth_date = birth.get_date_object() + + if birth_date and birth_date is not Date.EMPTY: + alive = probably_alive(self.person, self.r_db, Today()) + + death_date = _find_death_date(self.r_db, self.person) + if not alive and death_date is not None: + nyears = death_date - birth_date + nyears = nyears.format(precision=3, + dlocale=self.rlocale) + trow = Html("tr") + ( + Html("td", self._("Age at Death"), + class_="ColumnAttribute", inline=True), + Html("td", nyears, + class_="ColumnValue", inline=True) + ) + table += trow + + # return all three pieces to its caller + # do NOT combine before returning + return thumbnail, section_title, summaryarea + + def display_ind_events(self, place_lat_long): + """ + will create the events table + + @param: place_lat_long -- For use in Family Map Pages. This will be None + if called from Family pages, which do not + create a Family Map + """ + event_ref_list = self.person.get_event_ref_list() + if not event_ref_list: + return None + + # begin events division and section title + with Html("div", id="events", class_="subsection") as section: + section += Html("h4", self._("Events"), inline=True) + + # begin events table + with Html("table", class_="infolist eventlist") as table: + section += table + + thead = Html("thead") + table += thead + + # attach event header row + thead += self.event_header_row() + + tbody = Html("tbody") + table += tbody + + for evt_ref in event_ref_list: + event = self.r_db.get_event_from_handle(evt_ref.ref) + if event: + + # display event row + tbody += self.display_event_row(event, evt_ref, + place_lat_long, + True, True, + EventRoleType.PRIMARY) + return section + + def display_parent(self, handle, title, rel): + """ + This will display a parent ... + + @param: handle -- The person handle + @param: title -- Is the title of the web page + @param: rel -- The relation + """ + tcell1 = Html("td", title, class_="ColumnAttribute", inline=True) + tcell2 = Html("td", class_="ColumnValue", close=False, inline=True) + + tcell2 += self.new_person_link(handle, uplink=True) + + if rel and rel != ChildRefType(ChildRefType.BIRTH): + tcell2 += ''.join([' '] *3 + ['(%s)']) % str(rel) + + person = self.r_db.get_person_from_handle(handle) + birth = death = "" + if person: + bd_event = get_birth_or_fallback(self.r_db, person) + if bd_event: + birth = self.rlocale.get_date(bd_event.get_date_object()) + dd_event = get_death_or_fallback(self.r_db, person) + if dd_event: + death = self.rlocale.get_date(dd_event.get_date_object()) + + tcell3 = Html("td", birth, class_="ColumnDate", + inline=False, close=False, indent=False) + + tcell4 = Html("td", death, class_="ColumnDate", + inline=True, close=False, indent=False) + + tcell2 += tcell3 + tcell2 += tcell4 + + # return table columns to its caller + return tcell1, tcell2 + + def get_reln_in_family(self, ind, family): + """ + Display the relation of the indiv in the family + + @param: ind -- The person to use + @param: family -- The family + """ + child_handle = ind.get_handle() + child_ref_list = family.get_child_ref_list() + for child_ref in child_ref_list: + if child_ref.ref == child_handle: + return (child_ref.get_father_relation(), + child_ref.get_mother_relation()) + return (None, None) + + def display_ind_parent_family(self, birthmother, birthfather, family, + table, + first=False): + """ + Display the individual parent family + + @param: birthmother -- The birth mother + @param: birthfather -- The birth father + @param: family -- The family + @param: table -- The html document to complete + @param: first -- Is this the first indiv ? + """ + if not first: + trow = Html("tr") + (Html("td", " ", colspan=3, + inline=True)) + table += trow + + # get the father + father_handle = family.get_father_handle() + if father_handle: + if father_handle == birthfather: + # The parent may not be birth father in ths family, because it + # may be a step family. However, it will be odd to display the + # parent as anything other than "Father" + reln = self._("Father") + else: + # Stepfather may not always be quite right (for example, it may + # actually be StepFather-in-law), but it is too expensive to + # calculate out the correct relationship using the Relationship + # Calculator + reln = self._("Stepfather") + trow = Html("tr") + (self.display_parent(father_handle, reln, None)) + table += trow + + # get the mother + mother_handle = family.get_mother_handle() + if mother_handle: + if mother_handle == birthmother: + reln = self._("Mother") + else: + reln = self._("Stepmother") + trow = Html("tr") + (self.display_parent(mother_handle, reln, None)) + table += trow + + for child_ref in family.get_child_ref_list(): + child_handle = child_ref.ref + child = self.r_db.get_person_from_handle(child_handle) + if child: + if child == self.person: + reln = "" + else: + try: + # We have a try except block here, because the two + # people MUST be siblings for the called Relationship + # routines to work. Depending on your definition of + # sibling, we cannot necessarily guarantee that. + sibling_type = self.rel_class.get_sibling_type( + self.r_db, self.person, child) + + reln = self.rel_class.get_sibling_relationship_string( + sibling_type, self.person.gender, child.gender) + # We have a problem here : reln is never in the choosen + # language but in the default language. + # Does get_sibling_relationship_string work ? + reln = reln[0].upper() + reln[1:] + except: + reln = self._("Not siblings") + + val1 = "    " + reln = val1 + reln + # Now output reln, child_link, (frel, mrel) + frel = child_ref.get_father_relation() + mrel = child_ref.get_mother_relation() + if frel != ChildRefType.BIRTH or mrel != ChildRefType.BIRTH: + frelmrel = "(%s, %s)" % (str(frel), str(mrel)) + else: + frelmrel = "" + trow = Html("tr") + ( + Html("td", reln, class_="ColumnAttribute", inline=True)) + + tcell = Html("td", val1, class_="ColumnValue", inline=True) + tcell += self.display_child_link(child_handle) + + birth = death = "" + bd_event = get_birth_or_fallback(self.r_db, child) + if bd_event: + birth = self.rlocale.get_date(bd_event.get_date_object()) + dd_event = get_death_or_fallback(self.r_db, child) + if dd_event: + death = self.rlocale.get_date(dd_event.get_date_object()) + + tcell2 = Html("td", birth, class_="ColumnDate", + inline=True) + + tcell3 = Html("td", death, class_="ColumnDate", + inline=True) + + trow += tcell + trow += tcell2 + trow += tcell3 + + tcell = Html("td", frelmrel, class_="ColumnValue", + inline=True) + trow += tcell + table += trow + + def display_step_families(self, parent_handle, + family, + all_family_handles, + birthmother, birthfather, + table): + """ + Display step families + + @param: parent_handle -- The family parent handle to display + @param: family -- The family + @param: all_family_handles -- All known family handles + @param: birthmother -- The birth mother + @param: birthfather -- The birth father + @param: table -- The html document to complete + """ + if parent_handle: + parent = self.r_db.get_person_from_handle(parent_handle) + for parent_family_handle in parent.get_family_handle_list(): + if parent_family_handle not in all_family_handles: + parent_family = self.r_db.get_family_from_handle( + parent_family_handle) + self.display_ind_parent_family(birthmother, birthfather, + parent_family, table) + all_family_handles.append(parent_family_handle) + + def display_ind_center_person(self): + """ + Display the person's relationship to the center person + """ + center_person = self.r_db.get_person_from_gramps_id( + self.report.options['pid']) + relationship = self.rel_class.get_one_relationship(self.r_db, + self.person, + center_person) + if relationship == "": # No relation to display + return + + # begin center_person division + section = "" + with Html("div", class_="subsection", id="parents") as section: + message = self._("Relation to the center person") + message += " (" + name_format = self.report.options['name_format'] + primary_name = center_person.get_primary_name() + name = Name(primary_name) + name.set_display_as(name_format) + message += _nd.display_name(name) + message += ") : " + message += relationship + section += Html("h4", message, inline=True) + return section + + def display_ind_parents(self): + """ + Display a person's parents + """ + parent_list = self.person.get_parent_family_handle_list() + if not parent_list: + return None + + # begin parents division + with Html("div", class_="subsection", id="parents") as section: + section += Html("h4", self._("Parents"), inline=True) + + # begin parents table + with Html("table", class_="infolist") as table: + section += table + + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + trow.extend( + Html("th", label, class_=colclass, inline=True) + for (label, colclass) in [ + (self._("Relation to main person"), "ColumnAttribute"), + (self._("Name"), "ColumnValue"), + (self._("Birth date"), "ColumnValue"), + (self._("Death date"), "ColumnValue"), + (self._("Relation within this family " + "(if not by birth)"), + "ColumnValue") + ] + ) + + tbody = Html("tbody") + + all_family_handles = list(parent_list) + (birthmother, birthfather) = self.rel_class.get_birth_parents( + self.r_db, self.person) + + first = True + for family_handle in parent_list: + family = self.r_db.get_family_from_handle(family_handle) + if family: + # Display this family + self.display_ind_parent_family(birthmother, + birthfather, + family, tbody, first) + first = False + + if self.report.options['showhalfsiblings']: + # Display all families in which the parents are + # involved. This displays half siblings and step + # siblings + self.display_step_families( + family.get_father_handle(), family, + all_family_handles, + birthmother, birthfather, tbody) + self.display_step_families( + family.get_mother_handle(), family, + all_family_handles, + birthmother, birthfather, tbody) + table += tbody + return section + + def pedigree_person(self, person): + """ + will produce a hyperlink for a pedigree person ... + + @param: person -- The person + """ + hyper = self.new_person_link(person.handle, person=person, uplink=True) + return hyper + + def pedigree_family(self): + """ + Returns a family pedigree + """ + ped = [] + for family_handle in self.person.get_family_handle_list(): + rel_family = self.r_db.get_family_from_handle(family_handle) + spouse_handle = utils.find_spouse(self.person, rel_family) + if spouse_handle: + spouse = self.r_db.get_person_from_handle(spouse_handle) + pedsp = (Html("li", class_="spouse") + + self.pedigree_person(spouse) + ) + else: + pedsp = (Html("li", class_="spouse")) + ped += [pedsp] + childlist = rel_family.get_child_ref_list() + if childlist: + with Html("ol") as childol: + pedsp += [childol] + for child_ref in childlist: + child = self.r_db.get_person_from_handle(child_ref.ref) + if child: + childol += (Html("li") + + self.pedigree_person(child) + ) + return ped diff --git a/gramps/plugins/webreport/place.py b/gramps/plugins/webreport/place.py new file mode 100644 index 000000000..4d9c5c6e6 --- /dev/null +++ b/gramps/plugins/webreport/place.py @@ -0,0 +1,451 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + PlacePage - Place index page and individual Place pages +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from collections import defaultdict +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.lib import (PlaceType, Place) +from gramps.gen.plug.report import Bibliography +from gramps.plugins.lib.libhtml import Html +from gramps.gen.utils.place import conv_lat_lon +from gramps.gen.utils.location import get_main_location + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import (get_first_letters, first_letter, + alphabet_navigation, GOOGLE_MAPS, + primary_difference, _KEYPLACE, + get_index_letter, FULLCLEAR, + MARKER_PATH, OSM_MARKERS, MARKERS, + html_escape) + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +###################################################### +# # +# Place Pages # +# # +###################################################### +class PlacePages(BasePage): + """ + This class is responsible for displaying information about the 'Person' + database objects. It displays this information under the 'Events' + tab. It is told by the 'add_instances' call which 'Person's to display, + and remembers the list of persons. A single call to 'display_pages' + displays both the Event List (Index) page and all the Event + pages. + + The base class 'BasePage' is initialised once for each page that is + displayed. + """ + def __init__(self, report): + """ + @param: report -- The instance of the main report class for + this report + """ + BasePage.__init__(self, report, title="") + self.place_dict = defaultdict(set) + self.placemappages = None + self.mapservice = None + self.person = None + self.familymappages = None + self.googlemapkey = None + + def display_pages(self, title): + """ + Generate and output the pages under the Place tab, namely the place + index and the individual place pages. + + @param: title -- Is the title of the web page + """ + LOG.debug("obj_dict[Place]") + for item in self.report.obj_dict[Place].items(): + LOG.debug(" %s", str(item)) + with self.r_user.progress(_("Narrated Web Site Report"), + _("Creating place pages"), + len(self.report.obj_dict[Place]) + 1 + ) as step: + + self.placelistpage(self.report, title, + self.report.obj_dict[Place].keys()) + + for place_handle in self.report.obj_dict[Place]: + step() + self.placepage(self.report, title, place_handle) + + def placelistpage(self, report, title, place_handles): + """ + Create a place index + + @param: report -- The instance of the main report class for + this report + @param: title -- Is the title of the web page + @param: place_handles -- The handle for the place to add + """ + BasePage.__init__(self, report, title) + + output_file, sio = self.report.create_file("places") + placelistpage, head, body = self.write_header(self._("Places")) + ldatec = 0 + prev_letter = " " + + # begin places division + with Html("div", class_="content", id="Places") as placelist: + body += placelist + + # place list page message + msg = self._("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.") + placelist += Html("p", msg, id="description") + + # begin alphabet navigation + index_list = get_first_letters(self.r_db, place_handles, + _KEYPLACE, rlocale=self.rlocale) + alpha_nav = alphabet_navigation(index_list, self.rlocale) + if alpha_nav is not None: + placelist += alpha_nav + + # begin places table and table head + with Html("table", + class_="infolist primobjlist placelist") as table: + placelist += table + + # begin table head + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + trow.extend( + Html("th", label, class_=colclass, inline=True) + for (label, colclass) in [ + [self._("Letter"), "ColumnLetter"], + [self._("Place Name | Name"), "ColumnName"], + [self._("State/ Province"), "ColumnState"], + [self._("Country"), "ColumnCountry"], + [self._("Latitude"), "ColumnLatitude"], + [self._("Longitude"), "ColumnLongitude"] + ] + ) + + # bug 9495 : incomplete display of place hierarchy labels + def sort_by_place_name(obj): + """ sort by lower case place name. """ + name = self.report.obj_dict[Place][obj][1] + return name.lower() + + handle_list = sorted(place_handles, + key=lambda x: sort_by_place_name(x)) + first = True + + # begin table body + tbody = Html("tbody") + table += tbody + + for place_handle in handle_list: + place = self.r_db.get_place_from_handle(place_handle) + if place: + if place.get_change_time() > ldatec: + ldatec = place.get_change_time() + plc_title = self.report.obj_dict[Place][place_handle][1] + main_location = get_main_location(self.r_db, place) + + if plc_title and plc_title != " ": + letter = get_index_letter(first_letter(plc_title), + index_list, + self.rlocale) + else: + letter = ' ' + + trow = Html("tr") + tbody += trow + + tcell = Html("td", class_="ColumnLetter", inline=True) + trow += tcell + if first or primary_difference(letter, prev_letter, + self.rlocale): + first = False + prev_letter = letter + trow.attr = 'class = "BeginLetter"' + + ttle = self._("Places beginning " + "with letter %s") % letter + tcell += Html("a", letter, name=letter, title=ttle) + else: + tcell += " " + + trow += Html("td", + self.place_link( + place.get_handle(), + plc_title, place.get_gramps_id()), + class_="ColumnName") + + trow.extend( + Html("td", data or " ", class_=colclass, + inline=True) + for (colclass, data) in [ + ["ColumnState", + main_location.get(PlaceType.STATE, '')], + ["ColumnCountry", + main_location.get(PlaceType.COUNTRY, '')] + ] + ) + + tcell1 = Html("td", class_="ColumnLatitude", + inline=True) + tcell2 = Html("td", class_="ColumnLongitude", + inline=True) + trow += (tcell1, tcell2) + + if place.lat and place.long: + latitude, longitude = conv_lat_lon(place.lat, + place.long, + "DEG") + tcell1 += latitude + tcell2 += longitude + else: + tcell1 += ' ' + tcell2 += ' ' + + # add clearline for proper styling + # add footer section + footer = self.write_footer(ldatec) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(placelistpage, output_file, sio, ldatec) + + def placepage(self, report, title, place_handle): + """ + Create a place page + + @param: report -- The instance of the main report class for + this report + @param: title -- Is the title of the web page + @param: place_handle -- The handle for the place to add + """ + place = report.database.get_place_from_handle(place_handle) + if not place: + return None + BasePage.__init__(self, report, title, place.get_gramps_id()) + self.bibli = Bibliography() + place_name = self.report.obj_dict[Place][place_handle][1] + ldatec = place.get_change_time() + + output_file, sio = self.report.create_file(place_handle, "plc") + self.uplink = True + self.page_title = place_name + placepage, head, body = self.write_header(_("Places")) + + self.placemappages = self.report.options['placemappages'] + self.mapservice = self.report.options['mapservice'] + self.googlemapkey = self.report.options['googlemapkey'] + + # begin PlaceDetail Division + with Html("div", class_="content", id="PlaceDetail") as placedetail: + body += placedetail + + if self.create_media: + media_list = place.get_media_list() + thumbnail = self.disp_first_img_as_thumbnail(media_list, + place) + if thumbnail is not None: + placedetail += thumbnail + + # add section title + placedetail += Html("h3", + html_escape(place_name), + inline=True) + + # begin summaryarea division and places table + with Html("div", id='summaryarea') as summaryarea: + placedetail += summaryarea + + with Html("table", class_="infolist place") as table: + summaryarea += table + + # list the place fields + self.dump_place(place, table) + + # place gallery + if self.create_media: + placegallery = self.disp_add_img_as_gallery(media_list, place) + if placegallery is not None: + placedetail += placegallery + + # place notes + notelist = self.display_note_list(place.get_note_list()) + if notelist is not None: + placedetail += notelist + + # place urls + urllinks = self.display_url_list(place.get_url_list()) + if urllinks is not None: + placedetail += urllinks + + # add place map here + # Link to Gramps marker + fname = "/".join(['images', 'marker.png']) + marker_path = self.report.build_url_image("marker.png", + "images", self.uplink) + + if self.placemappages: + if place and (place.lat and place.long): + latitude, longitude = conv_lat_lon(place.get_latitude(), + place.get_longitude(), + "D.D8") + placetitle = place_name + + # add narrative-maps CSS... + fname = "/".join(["css", "narrative-maps.css"]) + url = self.report.build_url_fname(fname, None, self.uplink) + head += Html("link", href=url, type="text/css", + media="screen", rel="stylesheet") + + # add MapService specific javascript code + src_js = GOOGLE_MAPS + "api/js?sensor=false" + if self.mapservice == "Google": + if self.googlemapkey: + src_js += "&key=" + self.googlemapkey + head += Html("script", type="text/javascript", + src=src_js, inline=True) + else: + url = self.secure_mode + url += ("maxcdn.bootstrapcdn.com/bootstrap/3.3.7/" + "css/bootstrap.min.css") + head += Html("link", href=url, type="text/javascript", + rel="stylesheet") + src_js = self.secure_mode + src_js += ("ajax.googleapis.com/ajax/libs/jquery/1.9.1/" + "jquery.min.js") + head += Html("script", type="text/javascript", + src=src_js, inline=True) + src_js = self.secure_mode + src_js += "openlayers.org/en/v3.17.1/build/ol.js" + head += Html("script", type="text/javascript", + src=src_js, inline=True) + url = self.secure_mode + url += "openlayers.org/en/v3.17.1/css/ol.css" + head += Html("link", href=url, type="text/javascript", + rel="stylesheet") + src_js = self.secure_mode + src_js += ("maxcdn.bootstrapcdn.com/bootstrap/3.3.7/" + "js/bootstrap.min.js") + head += Html("script", type="text/javascript", + src=src_js, inline=True) + + # section title + placedetail += Html("h4", self._("Place Map"), inline=True) + + # begin map_canvas division + with Html("div", id="map_canvas", inline=True) as canvas: + placedetail += canvas + + # Begin inline javascript code because jsc is a + # docstring, it does NOT have to be properly indented + if self.mapservice == "Google": + with Html("script", type="text/javascript", + indent=False) as jsc: + head += jsc + + # Google adds Latitude/ Longitude to its maps... + plce = placetitle.replace("'", "\\'") + jsc += MARKER_PATH % marker_path + jsc += MARKERS % ([[plce, + latitude, + longitude, + 1]], + latitude, longitude, + 10) + + else: + # OpenStreetMap (OSM) adds Longitude/ Latitude + # to its maps, and needs a country code in + # lowercase letters... + with Html("script", type="text/javascript") as jsc: + canvas += jsc + #param1 = xml_lang()[3:5].lower() + jsc += MARKER_PATH % marker_path + jsc += OSM_MARKERS % ([[float(longitude), + float(latitude), + placetitle]], + longitude, latitude, 10) + + # add javascript function call to body element + body.attr += ' onload = "initialize();" ' + + # add div for popups. + with Html("div", id="popup", inline=True) as popup: + placedetail += popup + + # source references + srcrefs = self.display_ind_sources(place) + if srcrefs is not None: + placedetail += srcrefs + + # References list + ref_list = self.display_bkref_list(Place, place_handle) + if ref_list is not None: + placedetail += ref_list + + # add clearline for proper styling + # add footer section + footer = self.write_footer(ldatec) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(placepage, output_file, sio, ldatec) diff --git a/gramps/plugins/webreport/repository.py b/gramps/plugins/webreport/repository.py new file mode 100644 index 000000000..a9b3eaca9 --- /dev/null +++ b/gramps/plugins/webreport/repository.py @@ -0,0 +1,287 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + RepositoryPage - Repository index page and individual Repository pages +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from collections import defaultdict +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.lib import Repository +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import (FULLCLEAR, html_escape) + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +################################################# +# +# creates the Repository List Page and Repository Pages +# +################################################# +class RepositoryPages(BasePage): + """ + This class is responsible for displaying information about the 'Repository' + database objects. It displays this information under the 'Individuals' + tab. It is told by the 'add_instances' call which 'Repository's to display, + and remembers the list of persons. A single call to 'display_pages' + displays both the Individual List (Index) page and all the Individual + pages. + + The base class 'BasePage' is initialised once for each page that is + displayed. + """ + def __init__(self, report): + """ + @param: report -- The instance of the main report class for this report + """ + BasePage.__init__(self, report, title="") + self.repos_dict = defaultdict(set) + + def display_pages(self, title): + """ + Generate and output the pages under the Repository tab, namely the + repository index and the individual repository pages. + + @param: title -- Is the title of the web page + """ + LOG.debug("obj_dict[Person]") + for item in self.report.obj_dict[Repository].items(): + LOG.debug(" %s", str(item)) + + # set progress bar pass for Repositories + with self.r_user.progress(_("Narrated Web Site Report"), + _('Creating repository pages'), + len(self.report.obj_dict[Repository]) + 1 + ) as step: + # Sort the repositories + repos_dict = {} + for repo_handle in self.report.obj_dict[Repository]: + repository = self.r_db.get_repository_from_handle(repo_handle) + key = repository.get_name() + str(repository.get_gramps_id()) + repos_dict[key] = (repository, repo_handle) + + keys = sorted(repos_dict, key=self.rlocale.sort_key) + + # RepositoryListPage Class + self.repositorylistpage(self.report, title, repos_dict, keys) + + for index, key in enumerate(keys): + (repo, handle) = repos_dict[key] + + step() + self.repositorypage(self.report, title, repo, handle) + + def repositorylistpage(self, report, title, repos_dict, keys): + """ + Create Index for repositories + + @param: report -- The instance of the main report class + for this report + @param: title -- Is the title of the web page + @param: repos_dict -- The dictionary for all repositories + @param: keys -- The keys used to access repositories + """ + BasePage.__init__(self, report, title) + #inc_repos = self.report.options["inc_repository"] + + output_file, sio = self.report.create_file("repositories") + repolistpage, head, body = self.write_header(_("Repositories")) + + ldatec = 0 + # begin RepositoryList division + with Html("div", class_="content", + id="RepositoryList") as repositorylist: + body += repositorylist + + msg = self._("This page contains an index of " + "all the repositories in the " + "database, sorted by their title. " + "Clicking on a repositories’s title " + "will take you to that repositories’s page.") + repositorylist += Html("p", msg, id="description") + + # begin repositories table and table head + with Html("table", class_="infolist primobjlist repolist") as table: + repositorylist += table + + thead = Html("thead") + table += thead + + trow = Html("tr") + ( + Html("th", " ", class_="ColumnRowLabel", inline=True), + Html("th", self._("Type"), class_="ColumnType", + inline=True), + Html("th", self._("Repository |Name"), class_="ColumnName", + inline=True) + ) + thead += trow + + # begin table body + tbody = Html("tbody") + table += tbody + + for index, key in enumerate(keys): + (repo, handle) = repos_dict[key] + + trow = Html("tr") + tbody += trow + + # index number + trow += Html("td", index + 1, class_="ColumnRowLabel", + inline=True) + + # repository type + rtype = self._(repo.type.xml_str()) + trow += Html("td", rtype, class_="ColumnType", inline=True) + + # repository name and hyperlink + if repo.get_name(): + trow += Html("td", + self.repository_link(handle, + repo.get_name(), + repo.get_gramps_id(), + self.uplink), + class_="ColumnName") + ldatec = repo.get_change_time() + else: + trow += Html("td", "[ untitled ]", class_="ColumnName") + + # add clearline for proper styling + # add footer section + footer = self.write_footer(ldatec) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(repolistpage, output_file, sio, ldatec) + + def repositorypage(self, report, title, repo, handle): + """ + Create one page for one repository. + + @param: report -- The instance of the main report class for this report + @param: title -- Is the title of the web page + @param: repo -- the repository to use + @param: handle -- the handle to use + """ + gid = repo.get_gramps_id() + BasePage.__init__(self, report, title, gid) + ldatec = repo.get_change_time() + + output_file, sio = self.report.create_file(handle, 'repo') + self.uplink = True + repositorypage, head, body = self.write_header(_('Repositories')) + + # begin RepositoryDetail division and page title + with Html("div", class_="content", + id="RepositoryDetail") as repositorydetail: + body += repositorydetail + + # repository name + repositorydetail += Html("h3", html_escape(repo.name), + inline=True) + + # begin repository table + with Html("table", class_="infolist repolist") as table: + repositorydetail += table + + tbody = Html("tbody") + table += tbody + + if not self.noid and gid: + trow = Html("tr") + ( + Html("td", self._("Gramps ID"), + class_="ColumnAttribute", + inline=True), + Html("td", gid, class_="ColumnValue", inline=True) + ) + tbody += trow + + trow = Html("tr") + ( + Html("td", self._("Type"), class_="ColumnAttribute", + inline=True), + Html("td", self._(repo.get_type().xml_str()), + class_="ColumnValue", + inline=True) + ) + tbody += trow + + # repository: address(es)... + # repository addresses do NOT have Sources + repo_address = self.display_addr_list(repo.get_address_list(), + False) + if repo_address is not None: + repositorydetail += repo_address + + # repository: urllist + urllist = self.display_url_list(repo.get_url_list()) + if urllist is not None: + repositorydetail += urllist + + # reposity: notelist + notelist = self.display_note_list(repo.get_note_list()) + if notelist is not None: + repositorydetail += notelist + + # display Repository Referenced Sources... + ref_list = self.display_bkref_list(Repository, repo.get_handle()) + if ref_list is not None: + repositorydetail += ref_list + + # add clearline for proper styling + # add footer section + footer = self.write_footer(ldatec) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(repositorypage, output_file, sio, ldatec) diff --git a/gramps/plugins/webreport/source.py b/gramps/plugins/webreport/source.py new file mode 100644 index 000000000..17f71eadf --- /dev/null +++ b/gramps/plugins/webreport/source.py @@ -0,0 +1,306 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + SourcePage - Source index page and individual Source pages +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from collections import defaultdict +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.lib import Source +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import (FULLCLEAR, html_escape) + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb.source") +getcontext().prec = 8 + +################################################# +# +# creates the Source List Page and Source Pages +# +################################################# +class SourcePages(BasePage): + """ + This class is responsible for displaying information about the 'Source' + database objects. It displays this information under the 'Sources' + tab. It is told by the 'add_instances' call which 'Source's to display, + and remembers the list of persons. A single call to 'display_pages' + displays both the Individual List (Index) page and all the Individual + pages. + + The base class 'BasePage' is initialised once for each page that is + displayed. + """ + def __init__(self, report): + """ + @param: report -- The instance of the main report class for + this report + """ + BasePage.__init__(self, report, title="") + self.source_dict = defaultdict(set) + self.navigation = None + self.citationreferents = None + + def display_pages(self, title): + """ + Generate and output the pages under the Sources tab, namely the sources + index and the individual sources pages. + + @param: title -- Is the title of the web page + """ + LOG.debug("obj_dict[Source]") + for item in self.report.obj_dict[Source].items(): + LOG.debug(" %s", str(item)) + with self.r_user.progress(_("Narrated Web Site Report"), + _("Creating source pages"), + len(self.report.obj_dict[Source]) + 1 + ) as step: + self.sourcelistpage(self.report, title, + self.report.obj_dict[Source].keys()) + + for source_handle in self.report.obj_dict[Source]: + step() + self.sourcepage(self.report, title, source_handle) + + def sourcelistpage(self, report, title, source_handles): + """ + Generate and output the Sources index page. + + @param: report -- The instance of the main report class for + this report + @param: title -- Is the title of the web page + @param: source_handles -- A list of the handles of the sources to be + displayed + """ + BasePage.__init__(self, report, title) + + source_dict = {} + + output_file, sio = self.report.create_file("sources") + sourcelistpage, head, body = self.write_header(self._("Sources")) + + # begin source list division + with Html("div", class_="content", id="Sources") as sourceslist: + body += sourceslist + + # Sort the sources + for handle in source_handles: + source = self.r_db.get_source_from_handle(handle) + if source is not None: + key = source.get_title() + source.get_author() + key += str(source.get_gramps_id()) + source_dict[key] = (source, handle) + + keys = sorted(source_dict, key=self.rlocale.sort_key) + + msg = self._("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.") + sourceslist += Html("p", msg, id="description") + + # begin sourcelist table and table head + with Html("table", + class_="infolist primobjlist sourcelist") as table: + sourceslist += table + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + header_row = [ + (self._("Number"), "ColumnRowLabel"), + (self._("Author"), "ColumnAuthor"), + (self._("Source Name|Name"), "ColumnName")] + + trow.extend( + Html("th", label or " ", class_=colclass, inline=True) + for (label, colclass) in header_row + ) + + # begin table body + tbody = Html("tbody") + table += tbody + + for index, key in enumerate(keys): + source, source_handle = source_dict[key] + + trow = Html("tr") + ( + Html("td", index + 1, class_="ColumnRowLabel", + inline=True) + ) + tbody += trow + trow.extend( + Html("td", source.get_author(), class_="ColumnAuthor", + inline=True) + ) + trow.extend( + Html("td", self.source_link(source_handle, + source.get_title(), + source.get_gramps_id()), + class_="ColumnName") + ) + + # add clearline for proper styling + # add footer section + footer = self.write_footer(None) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(sourcelistpage, output_file, sio, 0) + + def sourcepage(self, report, title, source_handle): + """ + Generate and output an individual Source page. + + @param: report -- The instance of the main report class + for this report + @param: title -- Is the title of the web page + @param: source_handle -- The handle of the source to be output + """ + source = report.database.get_source_from_handle(source_handle) + BasePage.__init__(self, report, title, source.get_gramps_id()) + if not source: + return + + self.page_title = source.get_title() + + inc_repositories = self.report.options["inc_repository"] + self.navigation = self.report.options['navigation'] + self.citationreferents = self.report.options['citationreferents'] + + output_file, sio = self.report.create_file(source_handle, "src") + self.uplink = True + sourcepage, head, body = self.write_header( + "%s - %s" % (self._('Sources'), self.page_title)) + + ldatec = 0 + # begin source detail division + with Html("div", class_="content", id="SourceDetail") as sourcedetail: + body += sourcedetail + + media_list = source.get_media_list() + if self.create_media and media_list: + thumbnail = self.disp_first_img_as_thumbnail(media_list, + source) + if thumbnail is not None: + sourcedetail += thumbnail + + # add section title + sourcedetail += Html("h3", html_escape(source.get_title()), + inline=True) + + # begin sources table + with Html("table", class_="infolist source") as table: + sourcedetail += table + + tbody = Html("tbody") + table += tbody + + source_gid = False + if not self.noid and self.gid: + source_gid = source.get_gramps_id() + + # last modification of this source + ldatec = source.get_change_time() + + for (label, value) in [(self._("Gramps ID"), source_gid), + (self._("Author"), source.get_author()), + (self._("Abbreviation"), + source.get_abbreviation()), + (self._("Publication information"), + source.get_publication_info())]: + if value: + trow = Html("tr") + ( + Html("td", label, class_="ColumnAttribute", + inline=True), + Html("td", value, class_="ColumnValue", inline=True) + ) + tbody += trow + + # Source notes + notelist = self.display_note_list(source.get_note_list()) + if notelist is not None: + sourcedetail += notelist + + # additional media from Source (if any?) + if self.create_media and media_list: + sourcemedia = self.disp_add_img_as_gallery(media_list, source) + if sourcemedia is not None: + sourcedetail += sourcemedia + + # Source Data Map... + src_data_map = self.write_srcattr(source.get_attribute_list()) + if src_data_map is not None: + sourcedetail += src_data_map + + # Source Repository list + if inc_repositories: + repo_list = self.dump_repository_ref_list( + source.get_reporef_list()) + if repo_list is not None: + sourcedetail += repo_list + + # Source references list + ref_list = self.display_bkref_list(Source, source_handle) + if ref_list is not None: + sourcedetail += ref_list + + # add clearline for proper styling + # add footer section + footer = self.write_footer(ldatec) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(sourcepage, output_file, sio, ldatec) diff --git a/gramps/plugins/webreport/statistics.py b/gramps/plugins/webreport/statistics.py new file mode 100644 index 000000000..67bf86211 --- /dev/null +++ b/gramps/plugins/webreport/statistics.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + StatisticsPage +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.lib import (Person, Family, Event, Place, Source, + Citation, Repository) +from gramps.gen.plug.report import Bibliography +from gramps.gen.utils.file import media_path_full +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import FULLCLEAR + +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 +_ = glocale.translation.sgettext + +class StatisticsPage(BasePage): + """ + Create one page for statistics + """ + def __init__(self, report, title, step): + """ + @param: report -- The instance of the main report class + for this report + @param: title -- Is the title of the web page + """ + import posixpath + BasePage.__init__(self, report, title) + self.bibli = Bibliography() + self.uplink = False + self.report = report + # set the file name and open file + output_file, sio = self.report.create_file("statistics") + addressbookpage, head, body = self.write_header(_("Statistics")) + (males, + females, + unknown) = self.get_gender(report.database.iter_person_handles()) + + mobjects = report.database.get_number_of_media() + npersons = report.database.get_number_of_people() + nfamilies = report.database.get_number_of_families() + nsurnames = len(set(report.database.surname_list)) + notfound = [] + total_media = 0 + mbytes = "0" + chars = 0 + for media in report.database.iter_media(): + total_media += 1 + fullname = media_path_full(report.database, media.get_path()) + try: + chars += posixpath.getsize(fullname) + length = len(str(chars)) + if chars <= 999999: + mbytes = _("less than 1") + else: + mbytes = str(chars)[:(length-6)] + except OSError: + notfound.append(media.get_path()) + + + with Html("div", class_="content", id='EventDetail') as section: + section += Html("h3", self._("Database overview"), inline=True) + body += section + with Html("div", class_="content", id='subsection narrative') as sec11: + sec11 += Html("h4", self._("Individuals"), inline=True) + body += sec11 + with Html("div", class_="content", id='subsection narrative') as sec1: + sec1 += Html("br", self._("Number of individuals") + self.colon + + "%d" % npersons, inline=True) + sec1 += Html("br", self._("Males") + self.colon + + "%d" % males, inline=True) + sec1 += Html("br", self._("Females") + self.colon + + "%d" % females, inline=True) + sec1 += Html("br", self._("Individuals with unknown gender") + + self.colon + "%d" % unknown, inline=True) + body += sec1 + with Html("div", class_="content", id='subsection narrative') as sec2: + sec2 += Html("h4", self._("Family Information"), inline=True) + sec2 += Html("br", self._("Number of families") + self.colon + + "%d" % nfamilies, inline=True) + sec2 += Html("br", self._("Unique surnames") + self.colon + + "%d" % nsurnames, inline=True) + body += sec2 + with Html("div", class_="content", id='subsection narrative') as sec3: + sec3 += Html("h4", self._("Media Objects"), inline=True) + sec3 += Html("br", + self._("Total number of media object references") + + self.colon + "%d" % total_media, inline=True) + sec3 += Html("br", self._("Number of unique media objects") + + self.colon + "%d" % mobjects, inline=True) + sec3 += Html("br", self._("Total size of media objects") + + self.colon + + "%8s %s" % (mbytes, self._("Megabyte|MB")), + inline=True) + sec3 += Html("br", self._("Missing Media Objects") + + self.colon + "%d" % len(notfound), inline=True) + body += sec3 + with Html("div", class_="content", id='subsection narrative') as sec4: + sec4 += Html("h4", self._("Miscellaneous"), inline=True) + sec4 += Html("br", self._("Number of events") + self.colon + + "%d" % report.database.get_number_of_events(), + inline=True) + sec4 += Html("br", self._("Number of places") + self.colon + + "%d" % report.database.get_number_of_places(), + inline=True) + nsources = report.database.get_number_of_sources() + sec4 += Html("br", self._("Number of sources") + + self.colon + "%d" % nsources, + inline=True) + ncitations = report.database.get_number_of_citations() + sec4 += Html("br", self._("Number of citations") + + self.colon + "%d" % ncitations, + inline=True) + nrepo = report.database.get_number_of_repositories() + sec4 += Html("br", self._("Number of repositories") + + self.colon + "%d" % nrepo, + inline=True) + body += sec4 + + (males, + females, + unknown) = self.get_gender(self.report.bkref_dict[Person].keys()) + + origin = " :
    " + report.filter.get_name(self.rlocale) + with Html("div", class_="content", id='EventDetail') as section: + section += Html("h3", + self._("Narrative web content report for") + origin, + inline=True) + body += section + with Html("div", class_="content", id='subsection narrative') as sec5: + sec5 += Html("h4", self._("Individuals"), inline=True) + sec5 += Html("br", self._("Number of individuals") + self.colon + + "%d" % len(self.report.bkref_dict[Person]), + inline=True) + sec5 += Html("br", self._("Males") + self.colon + + "%d" % males, inline=True) + sec5 += Html("br", self._("Females") + self.colon + + "%d" % females, inline=True) + sec5 += Html("br", self._("Individuals with unknown gender") + + self.colon + "%d" % unknown, inline=True) + body += sec5 + with Html("div", class_="content", id='subsection narrative') as sec6: + sec6 += Html("h4", self._("Family Information"), inline=True) + sec6 += Html("br", self._("Number of families") + self.colon + + "%d" % len(self.report.bkref_dict[Family]), + inline=True) + body += sec6 + with Html("div", class_="content", id='subsection narrative') as sec7: + sec7 += Html("h4", self._("Miscellaneous"), inline=True) + sec7 += Html("br", self._("Number of events") + self.colon + + "%d" % len(self.report.bkref_dict[Event]), + inline=True) + sec7 += Html("br", self._("Number of places") + self.colon + + "%d" % len(self.report.bkref_dict[Place]), + inline=True) + sec7 += Html("br", self._("Number of sources") + self.colon + + "%d" % len(self.report.bkref_dict[Source]), + inline=True) + sec7 += Html("br", self._("Number of citations") + self.colon + + "%d" % len(self.report.bkref_dict[Citation]), + inline=True) + sec7 += Html("br", self._("Number of repositories") + self.colon + + "%d" % len(self.report.bkref_dict[Repository]), + inline=True) + body += sec7 + + # add fullclear for proper styling + # and footer section to page + footer = self.write_footer(None) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(addressbookpage, output_file, sio, 0) + + def get_gender(self, person_list): + """ + This function return the number of males, females and unknown gender + from a person list. + """ + males = 0 + females = 0 + unknown = 0 + for person_handle in person_list: + person = self.report.database.get_person_from_handle(person_handle) + gender = person.get_gender() + if gender == Person.MALE: + males += 1 + elif gender == Person.FEMALE: + females += 1 + else: + unknown += 1 + return (males, females, unknown) diff --git a/gramps/plugins/webreport/surname.py b/gramps/plugins/webreport/surname.py new file mode 100644 index 000000000..07ae2d6bd --- /dev/null +++ b/gramps/plugins/webreport/surname.py @@ -0,0 +1,265 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + SurnamePage - creates list of individuals with same surname +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.plug.report import utils +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import (name_to_md5, _NAME_STYLE_FIRST, + _find_birth_date, _find_death_date, + FULLCLEAR, html_escape) + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +################################################# +# +# create the page from SurnameListPage +# +################################################# +class SurnamePage(BasePage): + """ + This will create a list of individuals with the same surname + """ + def __init__(self, report, title, surname, ppl_handle_list): + """ + @param: report -- The instance of the main report class for + this report + @param: title -- Is the title of the web page + @param: surname -- The surname to use + @param: ppl_handle_list -- The list of people for whom we need to create + a page. + """ + BasePage.__init__(self, report, title) + + # module variables + showbirth = report.options['showbirth'] + showdeath = report.options['showdeath'] + showpartner = report.options['showpartner'] + showparents = report.options['showparents'] + + if surname == '': + surname = self._("") + + output_file, sio = self.report.create_file(name_to_md5(surname), "srn") + self.uplink = True + (surnamepage, head, + body) = self.write_header("%s - %s" % (self._("Surname"), surname)) + ldatec = 0 + + # begin SurnameDetail division + with Html("div", class_="content", id="SurnameDetail") as surnamedetail: + body += surnamedetail + + # section title + surnamedetail += Html("h3", html_escape(surname), inline=True) + + # feature request 2356: avoid genitive form + msg = self._("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.") % html_escape(surname) + surnamedetail += Html("p", msg, id="description") + + # begin surname table and thead + with Html("table", class_="infolist primobjlist surname") as table: + surnamedetail += table + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + # Name Column + trow += Html("th", self._("Given Name"), class_="ColumnName", + inline=True) + + if showbirth: + trow += Html("th", self._("Birth"), class_="ColumnDate", + inline=True) + + if showdeath: + trow += Html("th", self._("Death"), class_="ColumnDate", + inline=True) + + if showpartner: + trow += Html("th", self._("Partner"), + class_="ColumnPartner", + inline=True) + + if showparents: + trow += Html("th", self._("Parents"), + class_="ColumnParents", + inline=True) + + # begin table body + tbody = Html("tbody") + table += tbody + + for person_handle in sorted(ppl_handle_list, + key=self.sort_on_name_and_grampsid): + + person = self.r_db.get_person_from_handle(person_handle) + if person.get_change_time() > ldatec: + ldatec = person.get_change_time() + trow = Html("tr") + tbody += trow + + # firstname column + link = self.new_person_link(person_handle, uplink=True, + person=person, + name_style=_NAME_STYLE_FIRST) + trow += Html("td", link, class_="ColumnName") + + # birth column + if showbirth: + tcell = Html("td", class_="ColumnBirth", inline=True) + trow += tcell + + birth_date = _find_birth_date(self.r_db, person) + if birth_date is not None: + if birth_date.fallback: + tcell += Html('em', + self.rlocale.get_date(birth_date), + inline=True) + else: + tcell += self.rlocale.get_date(birth_date) + else: + tcell += " " + + # death column + if showdeath: + tcell = Html("td", class_="ColumnDeath", inline=True) + trow += tcell + + death_date = _find_death_date(self.r_db, person) + if death_date is not None: + if death_date.fallback: + tcell += Html('em', + self.rlocale.get_date(death_date), + inline=True) + else: + tcell += self.rlocale.get_date(death_date) + else: + tcell += " " + + # partner column + if showpartner: + tcell = Html("td", class_="ColumnPartner") + trow += tcell + family_list = person.get_family_handle_list() + if family_list: + fam_count = 0 + for family_handle in family_list: + fam_count += 1 + family = self.r_db.get_family_from_handle( + family_handle) + partner_handle = utils.find_spouse( + person, family) + if partner_handle: + link = self.new_person_link(partner_handle, + uplink=True) + if fam_count < len(family_list): + if isinstance(link, Html): + link.inside += "," + else: + link += ',' + tcell += link + else: + tcell += " " + + # parents column + if showparents: + parent_hdl_list = person.get_parent_family_handle_list() + if parent_hdl_list: + parent_hdl = parent_hdl_list[0] + fam = self.r_db.get_family_from_handle(parent_hdl) + f_id = fam.get_father_handle() + m_id = fam.get_mother_handle() + mother = father = None + if f_id: + father = self.r_db.get_person_from_handle(f_id) + if father: + father_name = self.get_name(father) + if m_id: + mother = self.r_db.get_person_from_handle(m_id) + if mother: + mother_name = self.get_name(mother) + if mother and father: + tcell = Html("span", father_name, + class_="father fatherNmother") + tcell += Html("span", mother_name, + class_="mother") + elif mother: + tcell = Html("span", mother_name, + class_="mother", inline=True) + elif father: + tcell = Html("span", father_name, + class_="father", inline=True) + samerow = False + else: + tcell = " " # pylint: disable=R0204 + samerow = True + trow += Html("td", tcell, + class_="ColumnParents", inline=samerow) + + # add clearline for proper styling + # add footer section + footer = self.write_footer(ldatec) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(surnamepage, output_file, sio, ldatec) diff --git a/gramps/plugins/webreport/surnamelist.py b/gramps/plugins/webreport/surnamelist.py new file mode 100644 index 000000000..a48e63c90 --- /dev/null +++ b/gramps/plugins/webreport/surnamelist.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + SurnameListPage - Index for first letters of surname +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import (get_first_letters, _KEYPERSON, + alphabet_navigation, html_escape, + sort_people, name_to_md5, + first_letter, get_index_letter, + primary_difference, FULLCLEAR) + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +################################################# +# +# Creates the Surname List page +# +################################################# +class SurnameListPage(BasePage): + """ + This class is responsible for displaying the list of Surnames + """ + ORDER_BY_NAME = 0 + ORDER_BY_COUNT = 1 + + def __init__(self, report, title, ppl_handle_list, + order_by=ORDER_BY_NAME, filename="surnames"): + """ + @param: report -- The instance of the main report class for + this report + @param: title -- Is the title of the web page + @param: ppl_handle_list -- The list of people for whom we need to create + a page. + @param: order_by -- The way to sort surnames : + Surnames or Surnames count + @param: filename -- The name to use for the Surnames page + """ + BasePage.__init__(self, report, title) + prev_surname = "" + prev_letter = " " + + if order_by == self.ORDER_BY_NAME: + output_file, sio = self.report.create_file(filename) + surnamelistpage, head, body = self.write_header(self._('Surnames')) + else: + output_file, sio = self.report.create_file("surnames_count") + (surnamelistpage, head, + body) = self.write_header(self._('Surnames by person count')) + + # begin surnames division + with Html("div", class_="content", id="surnames") as surnamelist: + body += surnamelist + + # page message + msg = self._('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.') + surnamelist += Html("p", msg, id="description") + + # add alphabet navigation... + # only if surname list not surname count + if order_by == self.ORDER_BY_NAME: + index_list = get_first_letters(self.r_db, ppl_handle_list, + _KEYPERSON, rlocale=self.rlocale) + alpha_nav = alphabet_navigation(index_list, self.rlocale) + if alpha_nav is not None: + surnamelist += alpha_nav + + if order_by == self.ORDER_BY_COUNT: + table_id = 'SortByCount' + else: + table_id = 'SortByName' + + # begin surnamelist table and table head + with Html("table", class_="infolist primobjlist surnamelist", + id=table_id) as table: + surnamelist += table + + thead = Html("thead") + table += thead + + trow = Html("tr") + thead += trow + + trow += Html("th", self._("Letter"), class_="ColumnLetter", + inline=True) + + # create table header surname hyperlink + fname = self.report.surname_fname + self.ext + tcell = Html("th", class_="ColumnSurname", inline=True) + trow += tcell + hyper = Html("a", self._("Surname"), + href=fname, title=self._("Surnames")) + tcell += hyper + + # create table header number of people hyperlink + fname = "surnames_count" + self.ext + tcell = Html("th", class_="ColumnQuantity", inline=True) + trow += tcell + num_people = self._("Number of People") + hyper = Html("a", num_people, href=fname, title=num_people) + tcell += hyper + + # begin table body + with Html("tbody") as tbody: + table += tbody + + ppl_handle_list = sort_people(self.r_db, ppl_handle_list, + self.rlocale) + if order_by == self.ORDER_BY_COUNT: + temp_list = {} + for (surname, data_list) in ppl_handle_list: + index_val = "%90d_%s" % (999999999-len(data_list), + surname) + temp_list[index_val] = (surname, data_list) + + lkey = self.rlocale.sort_key + ppl_handle_list = (temp_list[key] + for key in sorted(temp_list, + key=lkey)) + + first = True + first_surname = True + + for (surname, data_list) in ppl_handle_list: + + if surname and not surname.isspace(): + letter = first_letter(surname) + if order_by == self.ORDER_BY_NAME: + # There will only be an alphabetic index list if + # the ORDER_BY_NAME page is being generated + letter = get_index_letter(letter, index_list, + self.rlocale) + else: + letter = ' ' + surname = self._("") + + trow = Html("tr") + tbody += trow + + tcell = Html("td", class_="ColumnLetter", inline=True) + trow += tcell + + if first or primary_difference(letter, prev_letter, + self.rlocale): + first = False + prev_letter = letter + trow.attr = 'class = "BeginLetter"' + ttle = self._("Surnames beginning with " + "letter %s") % letter + hyper = Html("a", letter, name=letter, + title=ttle, inline=True) + tcell += hyper + elif first_surname or surname != prev_surname: + first_surname = False + tcell += " " + prev_surname = surname + + trow += Html("td", + self.surname_link(name_to_md5(surname), + #html_escape(surname)), + surname), + class_="ColumnSurname", inline=True) + + trow += Html("td", len(data_list), + class_="ColumnQuantity", inline=True) + + # create footer section + # add clearline for proper styling + footer = self.write_footer(None) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(surnamelistpage, + output_file, sio, 0) # 0 => current date modification + + def surname_link(self, fname, name, opt_val=None, uplink=False): + """ + Create a link to the surname page. + + @param: fname -- Path to the file name + @param: name -- Name to see in the link + @param: opt_val -- Option value to use + @param: uplink -- If True, then "../../../" is inserted in front of + the result. + """ + url = self.report.build_url_fname_html(fname, "srn", uplink) + hyper = Html("a", html_escape(name), href=url, + title=name, inline=True) + if opt_val is not None: + hyper += opt_val + + # return hyperlink to its caller + return hyper diff --git a/gramps/plugins/webreport/thumbnail.py b/gramps/plugins/webreport/thumbnail.py new file mode 100644 index 000000000..1d74d428f --- /dev/null +++ b/gramps/plugins/webreport/thumbnail.py @@ -0,0 +1,277 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Copyright (C) 2007-2009 Gary Burton +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2008-2009 Brian G. Matherly +# Copyright (C) 2008 Jason M. Simanek +# Copyright (C) 2008-2011 Rob G. Healey +# Copyright (C) 2010 Doug Blank +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2010-2017 Serge Noiraud +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2016 Allen Crider +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Narrative Web Page generator. + +Classe: + ThumbnailPreviewPage +""" +#------------------------------------------------ +# python modules +#------------------------------------------------ +from decimal import getcontext +import logging + +#------------------------------------------------ +# Gramps module +#------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.lib import Media +from gramps.plugins.lib.libhtml import Html + +#------------------------------------------------ +# specific narrative web import +#------------------------------------------------ +from gramps.plugins.webreport.basepage import BasePage +from gramps.plugins.webreport.common import (FULLCLEAR, html_escape) + +_ = glocale.translation.sgettext +LOG = logging.getLogger(".NarrativeWeb") +getcontext().prec = 8 + +class ThumbnailPreviewPage(BasePage): + """ + This class is responsible for displaying information about + the Thumbnails page. + """ + def __init__(self, report, title, cb_progress): + """ + @param: report -- The instance of the main report class + for this report + @param: title -- Is the title of the web page + @param: cb_progress -- The step used for the progress bar. + """ + BasePage.__init__(self, report, title) + self.create_thumbs_only = report.options['create_thumbs_only'] + # bug 8950 : it seems it's better to sort on desc + gid. + def sort_by_desc_and_gid(obj): + """ + Sort by media description and gramps ID + """ + return (obj.desc, obj.gramps_id) + + self.photo_keys = sorted(self.report.obj_dict[Media], + key=lambda x: sort_by_desc_and_gid( + self.r_db.get_media_from_handle(x))) + + if self.create_unused_media: + # add unused media + media_list = self.r_db.get_media_handles() + for media_ref in media_list: + if media_ref not in self.report.obj_dict[Media]: + self.photo_keys.append(media_ref) + + media_list = [] + for person_handle in self.photo_keys: + photo = self.r_db.get_media_from_handle(person_handle) + if photo: + if photo.get_mime_type().startswith("image"): + media_list.append((photo.get_description(), person_handle, + photo)) + + if self.create_thumbs_only: + self.copy_thumbnail(person_handle, photo) + + media_list.sort(key=lambda x: self.rlocale.sort_key(x[0])) + + # Create thumbnail preview page... + output_file, sio = self.report.create_file("thumbnails") + thumbnailpage, head, body = self.write_header(self._("Thumbnails")) + + with Html("div", class_="content", id="Preview") as previewpage: + body += previewpage + + msg = self._("This page displays a indexed list " + "of all the media objects " + "in this database. It is sorted by media title. " + "There is an index " + "of all the media objects in this database. " + "Clicking on a thumbnail " + "will take you to that image’s page.") + previewpage += Html("p", msg, id="description") + + with Html("table", class_="calendar") as table: + previewpage += table + + thead = Html("thead") + table += thead + + # page title... + trow = Html("tr") + thead += trow + + trow += Html("th", self._("Thumbnail Preview"), + class_="monthName", colspan=7, inline=True) + + # table header cells... + trow = Html("tr") + thead += trow + + ltrs = [" ", " ", " ", + " ", " ", " ", " "] + for ltr in ltrs: + trow += Html("th", ltr, class_="weekend", inline=True) + + tbody = Html("tbody") + table += tbody + + index, indexpos = 1, 0 + num_of_images = len(media_list) + num_of_rows = ((num_of_images // 7) + 1) + num_of_cols = 7 + grid_row = 0 + while grid_row < num_of_rows: + trow = Html("tr", id="RowNumber: %08d" % grid_row) + tbody += trow + + cols = 0 + while cols < num_of_cols and indexpos < num_of_images: + ptitle = media_list[indexpos][0] + person_handle = media_list[indexpos][1] + photo = media_list[indexpos][2] + + # begin table cell and attach to table row(trow)... + tcell = Html("td", class_="highlight weekend") + trow += tcell + + # attach index number... + numberdiv = Html("div", class_="date") + tcell += numberdiv + + # attach anchor name to date cell in upper right + # corner of grid... + numberdiv += Html("a", index, name=index, title=index, + inline=True) + + # begin unordered list and + # attach to table cell(tcell)... + unordered = Html("ul") + tcell += unordered + + # create thumbnail + (real_path, + newpath) = self.report.prepare_copy_media(photo) + newpath = self.report.build_url_fname(newpath) + + list_html = Html("li") + unordered += list_html + + # attach thumbnail to list... + list_html += self.thumb_hyper_image(newpath, "img", + person_handle, + ptitle) + + index += 1 + indexpos += 1 + cols += 1 + grid_row += 1 + + # if last row is incomplete, finish it off? + if grid_row == num_of_rows and cols < num_of_cols: + for emptycols in range(cols, num_of_cols): + trow += Html("td", class_="emptyDays", inline=True) + + # begin Thumbnail Reference section... + with Html("div", class_="subsection", id="references") as section: + body += section + section += Html("h4", self._("References"), inline=True) + + with Html("table", class_="infolist") as table: + section += table + + tbody = Html("tbody") + table += tbody + + index = 1 + for ptitle, person_handle, photo in media_list: + trow = Html("tr") + tbody += trow + + tcell1 = Html("td", + self.thumbnail_link(ptitle, index), + class_="ColumnRowLabel") + tcell2 = Html("td", ptitle, class_="ColumnName") + trow += (tcell1, tcell2) + + # increase index for row number... + index += 1 + + # increase progress meter... + cb_progress() + + # add body id element + body.attr = 'id ="ThumbnailPreview"' + + # add footer section + # add clearline for proper styling + footer = self.write_footer(None) + body += (FULLCLEAR, footer) + + # send page out for processing + # and close the file + self.xhtml_writer(thumbnailpage, output_file, sio, 0) + + + def thumbnail_link(self, name, index): + """ + creates a hyperlink for Thumbnail Preview Reference... + """ + return Html("a", index, title=html_escape(name), + href="#%d" % index) + + def thumb_hyper_image(self, thumbnail_url, subdir, fname, name): + """ + eplaces media_link() because it doesn't work for this instance + """ + name = html_escape(name) + url = "/".join(self.report.build_subdirs(subdir, + fname) + [fname]) + self.ext + + with Html("div", class_="content", id="ThumbnailPreview") as section: + with Html("div", class_="snapshot") as snapshot: + section += snapshot + + with Html("div", class_="thumbnail") as thumbnail: + snapshot += thumbnail + + if not self.create_thumbs_only: + thumbnail_link = Html("a", href=url, title=name) + ( + Html("img", src=thumbnail_url, alt=name) + ) + else: + thumbnail_link = Html("img", src=thumbnail_url, + alt=name) + thumbnail += thumbnail_link + return section diff --git a/gramps/plugins/webreport/webplugins.gpr.py b/gramps/plugins/webreport/webplugins.gpr.py index 135dfb831..29f6fd9c8 100644 --- a/gramps/plugins/webreport/webplugins.gpr.py +++ b/gramps/plugins/webreport/webplugins.gpr.py @@ -34,13 +34,13 @@ plg.id = 'navwebpage' plg.name = _("Narrated Web Site") plg.description = _("Produces web (HTML) pages for individuals, or a set of " "individuals") -plg.version = '1.0' +plg.version = '2.0' plg.gramps_target_version = MODULE_VERSION plg.status = STABLE plg.fname = 'narrativeweb.py' plg.ptype = REPORT -plg.authors = ["Donald N. Allingham", "Rob G. Healey"] -plg.authors_email = ["don@gramps-project.org", "robhealey1@gmail.com"] +plg.authors = ["Donald N. Allingham", "Rob G. Healey", "Serge Noiraud"] +plg.authors_email = ["don@gramps-project.org", "serge.noiraud@free.fr"] plg.category = CATEGORY_WEB plg.reportclass = 'NavWebReport' plg.optionclass = 'NavWebOptions'