1# -*- coding: utf-8 -*-
2#!/usr/bin/env python
3#
4# Gramps - a GTK+/GNOME based genealogy program
5#
6# Copyright (C) 2000-2007  Donald N. Allingham
7# Copyright (C) 2007       Johan Gonqvist <johan.gronqvist@gmail.com>
8# Copyright (C) 2007-2009  Gary Burton <gary.burton@zen.co.uk>
9# Copyright (C) 2007-2009  Stephane Charette <stephanecharette@gmail.com>
10# Copyright (C) 2008-2009  Brian G. Matherly
11# Copyright (C) 2008       Jason M. Simanek <jason@bohemianalps.com>
12# Copyright (C) 2008-2011  Rob G. Healey <robhealey1@gmail.com>
13# Copyright (C) 2010       Doug Blank <doug.blank@gmail.com>
14# Copyright (C) 2010       Jakim Friant
15# Copyright (C) 2010-      Serge Noiraud
16# Copyright (C) 2011       Tim G L Lyons
17# Copyright (C) 2013       Benny Malengier
18# Copyright (C) 2016       Allen Crider
19# Copyright (C) 2018       Theo van Rijn
20#
21# This program is free software; you can redistribute it and/or modify
22# it under the terms of the GNU General Public License as published by
23# the Free Software Foundation; either version 2 of the License, or
24# (at your option) any later version.
25#
26# This program is distributed in the hope that it will be useful,
27# but WITHOUT ANY WARRANTY; without even the implied warranty of
28# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29# GNU General Public License for more details.
30#
31# You should have received a copy of the GNU General Public License
32# along with this program; if not, write to the Free Software
33# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
34#
35
36"""
37Narrative Web Page generator.
38
39Classe:
40    BasePage - super class for producing a web page. This class is instantiated
41    once for each page. Provdes various common functions.
42"""
43#------------------------------------------------
44# python modules
45#------------------------------------------------
46from functools import partial
47import os
48import copy
49import datetime
50from decimal import getcontext
51
52#------------------------------------------------
53# Set up logging
54#------------------------------------------------
55import logging
56
57#------------------------------------------------
58# Gramps module
59#------------------------------------------------
60from gramps.gen.const import GRAMPS_LOCALE as glocale
61from gramps.gen.lib import (FamilyRelType, NoteType, NameType, Person,
62                            UrlType, Name, PlaceType, EventRoleType,
63                            Family, Citation, Place, Date)
64from gramps.gen.lib.date import Today
65from gramps.gen.const import PROGRAM_NAME, URL_HOMEPAGE
66from gramps.version import VERSION
67from gramps.gen.plug.report import Bibliography
68from gramps.gen.plug.report import utils
69from gramps.gen.utils.config import get_researcher
70from gramps.gen.utils.string import conf_strings
71from gramps.gen.utils.file import media_path_full
72from gramps.gen.utils.thumbnails import get_thumbnail_path
73from gramps.gen.display.name import displayer as _nd
74from gramps.gen.display.place import displayer as _pd
75from gramps.plugins.lib.libhtmlconst import _CC
76from gramps.gen.utils.db import get_birth_or_fallback, get_death_or_fallback
77from gramps.gen.datehandler import parser as _dp
78from gramps.plugins.lib.libhtml import Html, xml_lang
79from gramps.plugins.lib.libhtmlbackend import HtmlBackend, process_spaces
80from gramps.gen.utils.place import conv_lat_lon
81from gramps.gen.utils.location import get_main_location
82from gramps.plugins.webreport.common import (_NAME_STYLE_DEFAULT, HTTP,
83                                             _NAME_STYLE_FIRST, HTTPS,
84                                             _get_short_name,
85                                             add_birthdate, CSS, html_escape,
86                                             _NARRATIVESCREEN, _NARRATIVEPRINT,
87                                             FULLCLEAR, _has_webpage_extension)
88
89_ = glocale.translation.sgettext
90LOG = logging.getLogger(".NarrativeWeb")
91getcontext().prec = 8
92
93class BasePage: # pylint: disable=C1001
94    """
95    Manages all the functions, variables, and everything needed
96    for all of the classes contained within this plugin
97    """
98    def __init__(self, report, title, gid=None):
99        """
100        @param: report -- The instance of the main report class for
101                          this report
102        @param: title  -- Is the title of the web page
103        @param: gid    -- The family gramps ID
104        """
105        self.uplink = False
106        # class to do conversion of styled notes to html markup
107        self._backend = HtmlBackend()
108        self._backend.build_link = report.build_link
109
110        self.report = report
111        self.r_db = report.database
112        self.r_user = report.user
113        self.title_str = title
114        self.gid = gid
115        self.bibli = Bibliography()
116
117        self.page_title = ""
118
119        self.author = get_researcher().get_name()
120        if self.author:
121            self.author = self.author.replace(',,,', '')
122
123        # TODO. All of these attributes are not necessary, because we have
124        # also the options in self.options.  Besides, we need to check which
125        # are still required.
126        self.html_dir = report.options['target']
127        self.ext = report.options['ext']
128        self.noid = not report.options['inc_id']
129        self.linkhome = report.options['linkhome']
130        self.create_media = report.options['gallery']
131        self.create_unused_media = report.options['unused']
132        self.create_thumbs_only = report.options['create_thumbs_only']
133        self.inc_families = report.options['inc_families']
134        self.inc_events = report.options['inc_events']
135        self.usecms = report.options['usecms']
136        self.prevnext = report.options['prevnext']
137        self.target_uri = report.options['cmsuri']
138        self.usecal = report.options['usecal']
139        self.target_cal_uri = report.options['caluri']
140        self.extrapage = report.options['extrapage']
141        self.extrapagename = report.options['extrapagename']
142        self.familymappages = None
143        self.reference_sort = report.options['reference_sort']
144        lang = report.options['trans']
145        self.rlocale = report.set_locale(lang)
146        self._ = self.rlocale.translation.sgettext
147        self.colon = self._(':') # translators: needed for French, else ignore
148
149        if report.options['securesite']:
150            self.secure_mode = HTTPS
151        else:
152            self.secure_mode = HTTP
153
154    # Functions used when no Web Page plugin is provided
155    def add_instance(self, *param):
156        """
157        Add an instance
158        """
159        pass
160
161    def display_pages(self, title):
162        """
163        Display the pages
164        """
165        pass
166
167    def sort_on_name_and_grampsid(self, handle):
168        """ Used to sort on name and gramps ID. """
169        person = self.r_db.get_person_from_handle(handle)
170        name = _nd.display(person)
171        return (name, person.get_gramps_id())
172
173    def sort_on_given_and_birth(self, handle):
174        """ Used to sort on given name and birth date. """
175        person = self.r_db.get_person_from_handle(handle)
176        name = _nd.display_given(person)
177        bd_event = get_birth_or_fallback(self.r_db, person)
178        birth = ""
179        if bd_event:
180            birth_iso = str(bd_event.get_date_object()).replace('abt ', '')
181            # we need to remove abt, bef, aft, ...
182            birth = birth_iso.replace('aft ', '').replace('bef ', '')
183        return (name, birth)
184
185    def sort_on_grampsid(self, event_ref):
186        """
187        Sort on gramps ID
188        """
189        evt = self.r_db.get_event_from_handle(
190            event_ref.ref)
191        return evt.get_gramps_id()
192
193    def copy_thumbnail(self, handle, photo, region=None):
194        """
195        Given a handle (and optional region) make (if needed) an
196        up-to-date cache of a thumbnail, and call report.copy_file
197        to copy the cached thumbnail to the website.
198        Return the new path to the image.
199        """
200        to_dir = self.report.build_path('thumb', handle)
201        to_path = os.path.join(to_dir, handle) + (
202            ('%d,%d-%d,%d.png' % region) if region else '.png'
203            )
204
205        if photo.get_mime_type():
206            full_path = media_path_full(self.r_db, photo.get_path())
207            from_path = get_thumbnail_path(full_path,
208                                           photo.get_mime_type(),
209                                           region)
210            if not os.path.isfile(from_path):
211                from_path = CSS["Document"]["filename"]
212        else:
213            from_path = CSS["Document"]["filename"]
214        self.report.copy_file(from_path, to_path)
215        return to_path
216
217    def get_nav_menu_hyperlink(self, url_fname, nav_text):
218        """
219        Returns the navigation menu hyperlink
220        """
221        if url_fname == self.target_cal_uri:
222            uplink = False
223        else:
224            uplink = self.uplink
225
226        # check for web page file extension?
227        if not _has_webpage_extension(url_fname):
228            url_fname += self.ext
229
230        # get menu item url and begin hyperlink...
231        url = self.report.build_url_fname(url_fname, None, uplink)
232
233        return Html("a", nav_text, href=url, title=nav_text, inline=True)
234
235    def get_column_data(self, unordered, data_list, column_title):
236        """
237        Returns the menu column for Drop Down Menus and Drop Down Citations
238        """
239        if not data_list:
240            return
241
242        elif len(data_list) == 1:
243            url_fname, nav_text = data_list[0][0], data_list[0][1]
244            hyper = self.get_nav_menu_hyperlink(url_fname, nav_text)
245            unordered.extend(
246                Html("li", hyper, inline=True)
247            )
248        else:
249            col_list = Html("li") + (
250                Html("a", column_title, href="#",
251                     title=column_title, inline=True)
252            )
253            unordered += col_list
254
255            unordered1 = Html("ul")
256            col_list += unordered1
257
258            for url_fname, nav_text in data_list:
259                hyper = self.get_nav_menu_hyperlink(url_fname, nav_text)
260                unordered1.extend(Html("li", hyper, inline=True))
261
262    def display_relationships(self, individual, place_lat_long):
263        """
264        Displays a person's relationships ...
265
266        @param: family_handle_list -- families in this report database
267        @param: place_lat_long     -- for use in Family Map Pages.
268                                      This will be None if called from
269                                      Family pages, which do not create
270                                      a Family Map
271        """
272        family_list = individual.get_family_handle_list()
273        if not family_list:
274            return None
275
276        with Html("div", class_="subsection", id="families") as section:
277            section += Html("h4", self._("Families"), inline=True)
278
279            table_class = "infolist"
280            if len(family_list) > 1:
281                table_class += " fixed_subtables"
282            with Html("table", class_=table_class) as table:
283                section += table
284
285                for family_handle in family_list:
286                    family = self.r_db.get_family_from_handle(family_handle)
287                    if family:
288                        link = self.family_link(
289                            family_handle,
290                            self.report.obj_dict[Family][family_handle][1],
291                            gid=family.get_gramps_id(), uplink=True)
292                        link1 = Html("H4", link, class_="subsection")
293                        trow = Html("tr", class_="BeginFamily") + (
294                            Html("td", link1, class_="ColumnValue", colspan=3,
295                                 inline=True)
296                        )
297                        table += trow
298                        # find the spouse of the principal individual and
299                        # display that person
300                        sp_hdl = utils.find_spouse(individual, family)
301                        if sp_hdl:
302                            spouse = self.r_db.get_person_from_handle(sp_hdl)
303                            if spouse:
304                                table += self.display_spouse(spouse, family,
305                                                             place_lat_long)
306
307                        details = self.display_family_details(family,
308                                                              place_lat_long)
309                        if details is not None:
310                            table += details
311        return section
312
313    def display_family_relationships(self, family, place_lat_long):
314        """
315        Displays a family's relationships ...
316
317        @param: family         -- the family to be displayed
318        @param: place_lat_long -- for use in Family Map Pages. This will
319                                  be None if called from Family pages, which
320                                  do not create a Family Map
321        """
322        with Html("div", class_="subsection", id="families") as section:
323            section += Html("h4", self._("Families"), inline=True)
324
325            table_class = "infolist"
326            with Html("table", class_=table_class) as table:
327                section += table
328                for person_hdl in [family.get_father_handle(),
329                                   family.get_mother_handle()]:
330                    person = None
331                    if person_hdl:
332                        person = self.r_db.get_person_from_handle(person_hdl)
333                    if person:
334                        table += self.display_spouse(person,
335                                                     family, place_lat_long)
336
337                details = self.display_family_details(family, place_lat_long)
338                if details is not None:
339                    table += details
340        return section
341
342    def display_family_details(self, family, place_lat_long):
343        """
344        Display details about one family: family events, children, family LDS
345        ordinances, family attributes
346        """
347        table = None
348        birthorder = self.report.options["birthorder"]
349        # display family events; such as marriage and divorce...
350        family_events = family.get_event_ref_list()
351        if family_events:
352            trow = Html("tr") + (
353                Html("td", "&nbsp;", class_="ColumnType", inline=True),
354                Html("td", "&nbsp;", class_="ColumnAttribute", inline=True),
355                Html("td", self.format_family_events(family_events,
356                                                     place_lat_long),
357                     class_="ColumnValue")
358            )
359            table = trow
360
361        # If the families pages are not output, display family notes
362        if not self.inc_families:
363            notelist = family.get_note_list()
364            for notehandle in notelist:
365                note = self.r_db.get_note_from_handle(notehandle)
366                if note:
367                    trow = Html("tr") + (
368                        Html("td", "&nbsp;", class_="ColumnType", inline=True),
369                        Html("td", self._("Narrative"),
370                             class_="ColumnAttribute",
371                             inline=True),
372                        Html("td", self.get_note_format(note, True),
373                             class_="ColumnValue")
374                    )
375                    table = table + trow if table is not None else trow
376
377        childlist = family.get_child_ref_list()
378        if childlist:
379            trow = Html("tr") + (
380                Html("td", "&nbsp;", class_="ColumnType", inline=True),
381                Html("td", self._("Children"), class_="ColumnAttribute",
382                     inline=True)
383            )
384            table = table + trow if table is not None else trow
385
386            tcell = Html("td", class_="ColumnValue", close=False)
387            trow += tcell
388
389            with Html("table", class_="infolist eventlist") as table2:
390                thead = Html("thead")
391                table2 += thead
392                header = Html("tr")
393
394                header.extend(
395                    Html("th", label, class_=colclass, inline=True)
396                    for (label, colclass) in [
397                        [self._("Name"), "ColumnName"],
398                        [self._("Birth Date"), "ColumnDate"],
399                        [self._("Death Date"), "ColumnDate"],
400                        ]
401                )
402                thead += header
403
404                # begin table body
405                tbody = Html("tbody")
406                table2 += tbody
407
408                childlist = [child_ref.ref for child_ref in childlist]
409
410                # add individual's children event places to family map...
411                if self.familymappages:
412                    for handle in childlist:
413                        child = self.r_db.get_person_from_handle(handle)
414                        if child:
415                            self._get_event_place(child, place_lat_long)
416
417                children = add_birthdate(self.r_db, childlist, self.rlocale)
418                if birthorder:
419                    children = sorted(children)
420
421                tbody.extend((Html("tr", inline=True) +
422                              Html("td", inline=True, close=False) +
423                              self.display_child_link(chandle) +
424                              Html("td", birth, inline=True) +
425                              Html("td", death, inline=True))
426                             for birth_date, birth, death, chandle in children
427                            )
428            trow += table2
429
430        # family LDS ordinance list
431        family_lds_ordinance_list = family.get_lds_ord_list()
432        if family_lds_ordinance_list:
433            trow = Html("tr") + (
434                Html("td", "&nbsp;", class_="ColumnType", inline=True),
435                Html("td", self._("LDS Ordinance"), class_="ColumnAttribute",
436                     inline=True),
437                Html("td", self.dump_ordinance(family, "Family"),
438                     class_="ColumnValue")
439            )
440            table = table + trow if table is not None else trow
441
442        # Family Attribute list
443        family_attribute_list = family.get_attribute_list()
444        if family_attribute_list:
445            trow = Html("tr") + (
446                Html("td", "&nbsp;", class_="ColumnType", inline=True),
447                Html("td", self._("Attributes"), class_="ColumnAttribute",
448                     inline=True)
449            )
450            table = table + trow if table is not None else trow
451
452            tcell = Html("td", class_="ColumnValue")
453            trow += tcell
454
455            # we do not need the section variable for this instance
456            # of Attributes...
457            dummy, attrtable = self.display_attribute_header()
458            tcell += attrtable
459            self.display_attr_list(family_attribute_list, attrtable)
460        return table
461
462    def complete_people(self, tcell, first_person, handle_list, uplink=True):
463        """
464        completes the person column for classes EventListPage and EventPage
465
466        @param: tcell        -- table cell from its caller
467        @param: first_person -- Not used any more, done via css
468        @param: handle_list  -- handle list from the backlink of
469                                the event_handle
470        """
471        dummy_first_person = first_person
472        for (classname, handle) in handle_list:
473
474            # personal event
475            if classname == "Person":
476                tcell += Html("span", self.new_person_link(handle, uplink),
477                              class_="person", inline=True)
478
479            # family event
480            else:
481                _obj = self.r_db.get_family_from_handle(handle)
482                if _obj:
483
484                    # husband and spouse in this example,
485                    # are called father and mother
486                    husband_handle = _obj.get_father_handle()
487                    if husband_handle:
488                        hlink = self.new_person_link(husband_handle, uplink)
489                    spouse_handle = _obj.get_mother_handle()
490                    if spouse_handle:
491                        slink = self.new_person_link(spouse_handle, uplink)
492
493                    if spouse_handle and husband_handle:
494                        tcell += Html("span", hlink, class_="father",
495                                      inline=True)
496                        tcell += Html("span", slink, class_="mother",
497                                      inline=True)
498                    elif spouse_handle:
499                        tcell += Html("span", slink, class_="mother",
500                                      inline=True)
501                    elif husband_handle:
502                        tcell += Html("span", hlink, class_="father",
503                                      inline=True)
504        return tcell
505
506    def dump_attribute(self, attr):
507        """
508        dump attribute for object presented in display_attr_list()
509
510        @param: attr = attribute object
511        """
512        trow = Html("tr")
513
514        trow.extend(
515            Html("td", data or "&nbsp;", class_=colclass,
516                 inline=True if (colclass == "Type" or "Sources") else False)
517            for (data, colclass) in [
518                (str(attr.get_type()), "ColumnType"),
519                (attr.get_value(), "ColumnValue"),
520                (self.dump_notes(attr.get_note_list()), "ColumnNotes"),
521                (self.get_citation_links(attr.get_citation_list()),
522                 "ColumnSources")
523            ]
524        )
525        return trow
526
527    def get_citation_links(self, citation_handle_list):
528        """
529        get citation link from the citation handle list
530
531        @param: citation_handle_list = list of gen/lib/Citation
532        """
533        text = ""
534        for citation_handle in citation_handle_list:
535            citation = self.r_db.get_citation_from_handle(citation_handle)
536            if citation:
537                index, key = self.bibli.add_reference(citation)
538                id_ = "%d%s" % (index+1, key)
539                text += ' <a href="#sref%s">%s</a>' % (id_, id_)
540        return text
541
542    def get_note_format(self, note, link_prefix_up):
543        """
544        will get the note from the database, and will return either the
545        styled text or plain note
546        """
547        self.report.link_prefix_up = link_prefix_up
548
549        text = ""
550        if note is not None:
551            # retrieve the body of the note
552            note_text = note.get()
553
554            # styled notes
555            htmlnotetext = self.styled_note(
556                note.get_styledtext(), note.get_format(),
557                contains_html=(note.get_type() == NoteType.HTML_CODE))
558            text = htmlnotetext or Html("p", note_text)
559
560        # return text of the note to its callers
561        return text
562
563    def styled_note(self, styledtext, styled_format, contains_html=False):
564        """
565        styledtext : assumed a StyledText object to write
566        styled_format : = 0 : Flowed, = 1 : Preformatted
567        style_name : name of the style to use for default presentation
568        """
569        text = str(styledtext)
570
571        if not text:
572            return ''
573
574        s_tags = styledtext.get_tags()
575        htmllist = Html("div", class_="grampsstylednote")
576        if contains_html:
577            markuptext = self._backend.add_markup_from_styled(text,
578                                                              s_tags,
579                                                              split='\n',
580                                                              escape=False)
581            htmllist += markuptext
582        else:
583            markuptext = self._backend.add_markup_from_styled(text,
584                                                              s_tags,
585                                                              split='\n')
586            linelist = []
587            linenb = 1
588            for line in markuptext.split('\n'):
589                [line, sigcount] = process_spaces(line, styled_format)
590                if sigcount == 0:
591                    # The rendering of an empty paragraph '<p></p>'
592                    # is undefined so we use a non-breaking space
593                    if linenb == 1:
594                        linelist.append('&nbsp;')
595                    htmllist.extend(Html('p') + linelist)
596                    linelist = []
597                    linenb = 1
598                else:
599                    if linenb > 1:
600                        linelist[-1] += '<br />'
601                    linelist.append(line)
602                    linenb += 1
603            if linenb > 1:
604                htmllist.extend(Html('p') + linelist)
605            # if the last line was blank, then as well as outputting
606            # the previous para, which we have just done,
607            # we also output a new blank para
608            if sigcount == 0:
609                linelist = ["&nbsp;"]
610                htmllist.extend(Html('p') + linelist)
611        return htmllist
612
613    def dump_notes(self, notelist):
614        """
615        dump out of list of notes with very little elements of its own
616
617        @param: notelist -- list of notes
618        """
619        if not notelist:
620            return Html("div")
621
622        # begin unordered list
623        notesection = Html("div")
624        for notehandle in notelist:
625            this_note = self.r_db.get_note_from_handle(notehandle)
626            if this_note is not None:
627                notesection.extend(Html("i", self._(this_note.type.xml_str()),
628                                        class_="NoteType"))
629                notesection.extend(self.get_note_format(this_note, True))
630        return notesection
631
632    def event_header_row(self):
633        """
634        creates the event header row for all events
635        """
636        trow = Html("tr", close=None)
637        trow.extend(
638            Html("th", trans, class_=colclass, inline=True)
639            for trans, colclass in  [
640                (self._("Event"), "ColumnEvent"),
641                (self._("Date"), "ColumnDate"),
642                (self._("Place"), "ColumnPlace"),
643                (self._("Description"), "ColumnDescription"),
644                (self._("Sources"), "ColumnSources")]
645        )
646        trow += Html("/tr", close=None)
647        return trow
648
649    def display_event_row(self, event, event_ref, place_lat_long,
650                          uplink, hyperlink, omit):
651        """
652        display the event row for IndividualPage
653
654        @param: evt            -- Event object from report database
655        @param: evt_ref        -- Event reference
656        @param: place_lat_long -- For use in Family Map Pages. This will be
657                                  None if called from Family pages, which do
658                                  not create a Family Map
659        @param: uplink         -- If True, then "../../../" is inserted in
660                                  front of the result.
661        @param: hyperlink      -- Add a hyperlink or not
662        @param: omit           -- Role to be omitted in output
663        """
664        event_gid = event.get_gramps_id()
665
666        place_handle = event.get_place_handle()
667        if place_handle:
668            place = self.r_db.get_place_from_handle(place_handle)
669            if place:
670                self.append_to_place_lat_long(place, event, place_lat_long)
671
672        # begin event table row
673        trow = Html("tr")
674
675        # get event type and hyperlink to it or not?
676        etype = self._(event.get_type().xml_str())
677
678        event_role = event_ref.get_role()
679        if not event_role == omit:
680            etype += " (%s)" % event_role
681        event_hyper = self.event_link(event_ref.ref,
682                                      etype,
683                                      event_gid,
684                                      uplink) if hyperlink else etype
685        trow += Html("td", event_hyper, class_="ColumnEvent")
686
687        # get event data
688        event_data = self.get_event_data(event, event_ref, uplink)
689
690        trow.extend(
691            Html("td", data or "&nbsp;", class_=colclass,
692                 inline=(not data or colclass == "ColumnDate"))
693            for (label, colclass, data) in event_data
694        )
695
696        trow2 = Html("tr")
697        trow2 += Html("td", "", class_="ColumnEvent")
698        # get event source references
699        srcrefs = self.get_citation_links(event.get_citation_list()) or "&nbsp;"
700        trow += Html("td", srcrefs, class_="ColumnSources", rowspan=2)
701
702        # get event notes
703        notelist = event.get_note_list()[:]  # we don't want to modify
704                                             # cached original
705        notelist.extend(event_ref.get_note_list())
706        htmllist = self.dump_notes(notelist)
707
708        # if the event or event reference has an attribute attached to it,
709        # get the text and format it correctly?
710        attrlist = event.get_attribute_list()[:]  # we don't want to modify
711                                                  # cached original
712        attrlist.extend(event_ref.get_attribute_list())
713        for attr in attrlist:
714            htmllist.extend(Html("p",
715                                 _("%(str1)s: %(str2)s") % {
716                                     'str1' : Html("b", attr.get_type()),
717                                     'str2' : attr.get_value()
718                                     }))
719
720            #also output notes attached to the attributes
721            notelist = attr.get_note_list()
722            if notelist:
723                htmllist.extend(self.dump_notes(notelist))
724
725        trow2 += Html("td", htmllist, class_="ColumnNotes", colspan=3)
726
727        trow += trow2
728        # return events table row to its callers
729        return trow
730
731    def append_to_place_lat_long(self, place, event, place_lat_long):
732        """
733        Create a list of places with coordinates.
734
735        @param: place_lat_long -- for use in Family Map Pages. This will be
736                                  None if called from Family pages, which do
737                                  not create a Family Map
738        """
739        if place_lat_long is None:
740            return
741        place_handle = place.get_handle()
742        event_date = event.get_date_object()
743
744        # 0 = latitude, 1 = longitude, 2 - placetitle,
745        # 3 = place handle, 4 = event date, 5 = event type
746        found = any(data[3] == place_handle and data[4] == event_date
747                    for data in place_lat_long)
748        if not found:
749            placetitle = _pd.display(self.r_db, place)
750            latitude = place.get_latitude()
751            longitude = place.get_longitude()
752            if latitude and longitude:
753                latitude, longitude = conv_lat_lon(latitude, longitude, "D.D8")
754                if latitude is not None:
755                    etype = event.get_type()
756                    place_lat_long.append([latitude, longitude, placetitle,
757                                           place_handle, event_date, etype])
758
759    def _get_event_place(self, person, place_lat_long):
760        """
761        Retrieve from a person their events, and places for family map
762
763        @param: person         -- Person object from the database
764        @param: place_lat_long -- For use in Family Map Pages. This will be
765                                  None if called from Family pages, which do
766                                  not create a Family Map
767        """
768        if not person:
769            return
770
771        # check to see if this person is in the report database?
772        use_link = self.report.person_in_webreport(person.get_handle())
773        if use_link:
774            evt_ref_list = person.get_event_ref_list()
775            if evt_ref_list:
776                for evt_ref in evt_ref_list:
777                    event = self.r_db.get_event_from_handle(evt_ref.ref)
778                    if event:
779                        pl_handle = event.get_place_handle()
780                        if pl_handle:
781                            place = self.r_db.get_place_from_handle(pl_handle)
782                            if place:
783                                self.append_to_place_lat_long(place, event,
784                                                              place_lat_long)
785
786    def family_link(self, family_handle, name, gid=None, uplink=False):
787        """
788        Create the url and link for FamilyPage
789
790        @param: family_handle -- The handle for the family to link
791        @param: name          -- The family name
792        @param: gid           -- The family gramps ID
793        @param: uplink        -- If True, then "../../../" is inserted in
794                                 front of the result.
795        """
796        name = html_escape(name)
797        if not self.noid and gid:
798            gid_html = Html("span", " [%s]" % gid, class_="grampsid",
799                            inline=True)
800        else:
801            gid_html = ""
802
803        result = self.report.obj_dict.get(Family).get(family_handle)
804        if result is None:
805            # the family is not included in the webreport
806            return name + str(gid_html)
807
808        url = self.report.build_url_fname(result[0], uplink=uplink)
809        hyper = Html("a", name, href=url, title=name)
810        hyper += gid_html
811        return hyper
812
813    def get_family_string(self, family):
814        """
815        Unused method ???
816        Returns a hyperlink for each person linked to the Family Page
817
818        @param: family -- The family
819        """
820        husband, spouse = [False]*2
821
822        husband_handle = family.get_father_handle()
823
824        if husband_handle:
825            husband = self.r_db.get_person_from_handle(husband_handle)
826        else:
827            husband = None
828
829        spouse_handle = family.get_mother_handle()
830        if spouse_handle:
831            spouse = self.r_db.get_person_from_handle(spouse_handle)
832        else:
833            spouse = None
834
835        if husband:
836            husband_name = self.get_name(husband)
837            hlink = self.family_link(family.get_handle(),
838                                     husband_name, uplink=self.uplink)
839        if spouse:
840            spouse_name = self.get_name(spouse)
841            slink = self.family_link(family.get_handle(),
842                                     spouse_name, uplink=self.uplink)
843
844        title_str = ''
845        if husband and spouse:
846            title_str = '%s ' % hlink + self._("and") + ' %s' % slink
847        elif husband:
848            title_str = '%s ' % hlink
849        elif spouse:
850            title_str = '%s ' % slink
851        return title_str
852
853    def event_link(self, event_handle, event_title, gid=None, uplink=False):
854        """
855        Creates a hyperlink for an event based on its type
856
857        @param: event_handle -- Event handle
858        @param: event_title  -- Event title
859        @param: gid          -- The gramps ID for the event
860        @param: uplink       -- If True, then "../../../" is inserted in front
861                                of the result.
862        """
863        if not self.inc_events:
864            return event_title
865
866        url = self.report.build_url_fname_html(event_handle, "evt", uplink)
867        hyper = Html("a", event_title, href=url, title=event_title)
868
869        if not self.noid and gid:
870            hyper += Html("span", " [%s]" % gid, class_="grampsid", inline=True)
871        return hyper
872
873    def format_family_events(self, event_ref_list, place_lat_long):
874        """
875        displays the event row for events such as marriage and divorce
876
877        @param: event_ref_list -- List of events reference
878        @param: place_lat_long -- For use in Family Map Pages. This will be
879                                  None if called from Family pages, which do
880                                  not create a Family Map
881        """
882        with Html("table", class_="infolist eventlist") as table:
883            thead = Html("thead")
884            table += thead
885
886            # attach event header row
887            thead += self.event_header_row()
888
889            # begin table body
890            tbody = Html("tbody")
891            table += tbody
892
893            for evt_ref in event_ref_list:
894                event = self.r_db.get_event_from_handle(evt_ref.ref)
895
896                # add event body row
897                tbody += self.display_event_row(event, evt_ref, place_lat_long,
898                                                uplink=True, hyperlink=True,
899                                                omit=EventRoleType.FAMILY)
900        return table
901
902    def get_event_data(self, evt, evt_ref,
903                       uplink, gid=None):
904        """
905        retrieve event data from event and evt_ref
906
907        @param: evt     -- Event from database
908        @param: evt_ref -- Event reference
909        @param: uplink  -- If True, then "../../../" is inserted in front of
910                           the result.
911        """
912        dummy_evt_ref = evt_ref
913        dummy_gid = gid
914        place = None
915        place_handle = evt.get_place_handle()
916        if place_handle:
917            place = self.r_db.get_place_from_handle(place_handle)
918
919        place_hyper = None
920        if place:
921            place_name = _pd.display(self.r_db, place, evt.get_date_object())
922            place_hyper = self.place_link(place_handle, place_name,
923                                          uplink=uplink)
924
925        evt_desc = evt.get_description()
926
927        # wrap it all up and return to its callers
928        # position 0 = translatable label, position 1 = column class
929        # position 2 = data
930        return [(self._("Date"), "ColumnDate",
931                 self.rlocale.get_date(evt.get_date_object())),
932                (self._("Place"), "ColumnPlace", place_hyper),
933                (self._("Description"), "ColumnDescription", evt_desc)]
934
935    def dump_ordinance(self, ldsobj, ldssealedtype):
936        """
937        will dump the LDS Ordinance information for either
938        a person or a family ...
939
940        @param: ldsobj        -- Either person or family
941        @param: ldssealedtype -- Either Sealed to Family or Spouse
942        """
943        dummy_ldssealedtype = ldssealedtype
944        objectldsord = ldsobj.get_lds_ord_list()
945        if not objectldsord:
946            return None
947
948        # begin LDS ordinance table and table head
949        with Html("table", class_="infolist ldsordlist") as table:
950            thead = Html("thead")
951            table += thead
952
953            # begin HTML row
954            trow = Html("tr")
955            thead += trow
956
957            trow.extend(
958                Html("th", label, class_=colclass, inline=True)
959                for (label, colclass) in [
960                    [self._("Type"), "ColumnLDSType"],
961                    [self._("Date"), "ColumnDate"],
962                    [self._("Temple"), "ColumnLDSTemple"],
963                    [self._("Place"), "ColumnLDSPlace"],
964                    [self._("Status"), "ColumnLDSStatus"],
965                    [self._("Sources"), "ColumnLDSSources"]
966                ]
967            )
968
969            # start table body
970            tbody = Html("tbody")
971            table += tbody
972
973            for ordobj in objectldsord:
974                place_hyper = "&nbsp;"
975                place_handle = ordobj.get_place_handle()
976                if place_handle:
977                    place = self.r_db.get_place_from_handle(place_handle)
978                    if place:
979                        place_title = _pd.display(self.r_db, place)
980                        place_hyper = self.place_link(
981                            place_handle, place_title,
982                            place.get_gramps_id(), uplink=True)
983
984                # begin ordinance rows
985                trow = Html("tr")
986
987                trow.extend(
988                    Html("td", value or "&nbsp;", class_=colclass,
989                         inline=(not value or colclass == "ColumnDate"))
990                    for (value, colclass) in [
991                        (ordobj.type2xml(), "ColumnType"),
992                        (self.rlocale.get_date(ordobj.get_date_object()),
993                         "ColumnDate"),
994                        (ordobj.get_temple(), "ColumnLDSTemple"),
995                        (place_hyper, "ColumnLDSPlace"),
996                        (ordobj.get_status(), "ColumnLDSStatus"),
997                        (self.get_citation_links(ordobj.get_citation_list()),
998                         "ColumnSources")
999                    ]
1000                )
1001                tbody += trow
1002        return table
1003
1004    def write_srcattr(self, srcattr_list):
1005        """
1006        Writes out the srcattr for the different objects
1007
1008        @param: srcattr_list -- List of source attributes
1009        """
1010        if not srcattr_list:
1011            return None
1012
1013        # begin data map division and section title...
1014        with Html("div", class_="subsection", id="data_map") as section:
1015            section += Html("h4", self._("Attributes"), inline=True)
1016
1017            with Html("table", class_="infolist") as table:
1018                section += table
1019
1020                thead = Html("thead")
1021                table += thead
1022
1023                trow = Html("tr") + (
1024                    Html("th", self._("Key"), class_="ColumnAttribute",
1025                         inline=True),
1026                    Html("th", self._("Value"), class_="ColumnValue",
1027                         inline=True)
1028                )
1029                thead += trow
1030
1031                tbody = Html("tbody")
1032                table += tbody
1033
1034                for srcattr in srcattr_list:
1035                    trow = Html("tr") + (
1036                        Html("td", str(srcattr.get_type()),
1037                             class_="ColumnAttribute", inline=True),
1038                        Html("td", srcattr.get_value(),
1039                             class_="ColumnValue", inline=True)
1040                    )
1041                    tbody += trow
1042        return section
1043
1044    def source_link(self, source_handle, source_title,
1045                    gid=None, cindex=None, uplink=False):
1046        """
1047        Creates a link to the source object
1048
1049        @param: source_handle -- Source handle from database
1050        @param: source_title  -- Title from the source object
1051        @param: gid           -- Source gramps id from the source object
1052        @param: cindex        -- Count index
1053        @param: uplink        -- If True, then "../../../" is inserted in
1054                                 front of the result.
1055        """
1056        url = self.report.build_url_fname_html(source_handle, "src", uplink)
1057        hyper = Html("a", source_title,
1058                     href=url,
1059                     title=source_title)
1060
1061        # if not None, add name reference to hyperlink element
1062        if cindex:
1063            hyper.attr += ' name ="sref%d"' % cindex
1064
1065        # add Gramps ID
1066        if not self.noid and gid:
1067            hyper += Html("span", ' [%s]' % gid, class_="grampsid", inline=True)
1068        return hyper
1069
1070    def display_addr_list(self, addrlist, showsrc):
1071        """
1072        Display a person's or repository's addresses ...
1073
1074        @param: addrlist -- a list of address handles
1075        @param: showsrc  -- True = show sources
1076                            False = do not show sources
1077                            None = djpe
1078        """
1079        if not addrlist:
1080            return None
1081
1082        # begin addresses division and title
1083        with Html("div", class_="subsection", id="Addresses") as section:
1084            section += Html("h4", self._("Addresses"), inline=True)
1085
1086            # write out addresses()
1087            section += self.dump_addresses(addrlist, showsrc)
1088
1089        # return address division to its caller
1090        return section
1091
1092    def dump_addresses(self, addrlist, showsrc):
1093        """
1094        will display an object's addresses, url list, note list,
1095        and source references.
1096
1097        @param: addrlist = either person or repository address list
1098        @param: showsrc = True  --  person and their sources
1099                          False -- repository with no sources
1100                          None  -- Address Book address with sources
1101        """
1102        if not addrlist:
1103            return None
1104
1105        # begin summaryarea division
1106        with Html("div", id="AddressTable") as summaryarea:
1107
1108            # begin address table
1109            with Html("table") as table:
1110                summaryarea += table
1111
1112                # get table class based on showsrc
1113                if showsrc is True:
1114                    table.attr = 'class = "infolist addrlist"'
1115                elif showsrc is False:
1116                    table.attr = 'class = "infolist repolist"'
1117                else:
1118                    table.attr = 'class = "infolist addressbook"'
1119
1120                # begin table head
1121                thead = Html("thead")
1122                table += thead
1123
1124                trow = Html("tr")
1125                thead += trow
1126
1127                addr_header = [[self._("Date"), "Date"],
1128                               [self._("Street"), "StreetAddress"],
1129                               [self._("Locality"), "Locality"],
1130                               [self._("City"), "City"],
1131                               [self._("State/ Province"), "State"],
1132                               [self._("County"), "County"],
1133                               [self._("Postal Code"), "Postalcode"],
1134                               [self._("Country"), "Cntry"],
1135                               [self._("Phone"), "Phone"]]
1136
1137                # True, False, or None ** see docstring for explanation
1138                if showsrc in [True, None]:
1139                    addr_header.append([self._("Sources"), "Sources"])
1140
1141                trow.extend(
1142                    Html("th", self._(label),
1143                         class_="Colummn" + colclass, inline=True)
1144                    for (label, colclass) in addr_header
1145                )
1146
1147                # begin table body
1148                tbody = Html("tbody")
1149                table += tbody
1150
1151                # get address list from an object; either repository or person
1152                for address in addrlist:
1153
1154                    trow = Html("tr")
1155                    tbody += trow
1156
1157                    addr_data_row = [
1158                        (self.rlocale.get_date(address.get_date_object()),
1159                         "ColumnDate"),
1160                        (address.get_street(), "ColumnStreetAddress"),
1161                        (address.get_locality(), "ColumnLocality"),
1162                        (address.get_city(), "ColumnCity"),
1163                        (address.get_state(), "ColumnState"),
1164                        (address.get_county(), "ColumnCounty"),
1165                        (address.get_postal_code(), "ColumnPostalCode"),
1166                        (address.get_country(), "ColumnCntry"),
1167                        (address.get_phone(), "ColumnPhone")
1168                    ]
1169
1170                    # get source citation list
1171                    if showsrc in [True, None]:
1172                        addr_data_row.append(
1173                            [self.get_citation_links(
1174                                address.get_citation_list()),
1175                             "ColumnSources"])
1176
1177                    trow.extend(
1178                        Html("td", value or "&nbsp;",
1179                             class_=colclass, inline=True)
1180                        for (value, colclass) in addr_data_row
1181                    )
1182
1183                    # address: notelist
1184                    if showsrc is not None:
1185                        notelist = self.display_note_list(
1186                            address.get_note_list())
1187                        if notelist is not None:
1188                            summaryarea += notelist
1189        return summaryarea
1190
1191    def addressbook_link(self, person_handle, uplink=False):
1192        """
1193        Creates a hyperlink for an address book link based on person's handle
1194
1195        @param: person_handle -- Person's handle from the database
1196        @param: uplink        -- If True, then "../../../" is inserted in
1197                                 front of the result.
1198        """
1199        url = self.report.build_url_fname_html(person_handle, "addr", uplink)
1200        person = self.r_db.get_person_from_handle(person_handle)
1201        person_name = self.get_name(person)
1202
1203        # return addressbook hyperlink to its caller
1204        return Html("a", person_name, href=url,
1205                    title=html_escape(person_name))
1206
1207    def get_name(self, person, maiden_name=None):
1208        """ I5118
1209
1210        Return person's name, unless maiden_name given, unless married_name
1211        listed.
1212
1213        @param: person      -- person object from database
1214        @param: maiden_name -- Female's family surname
1215        """
1216        # get name format for displaying names
1217        name_format = self.report.options['name_format']
1218
1219        # Get all of a person's names
1220        primary_name = person.get_primary_name()
1221        married_name = None
1222        names = [primary_name] + person.get_alternate_names()
1223        for name in names:
1224            if int(name.get_type()) == NameType.MARRIED:
1225                married_name = name
1226                break # use first
1227
1228        # Now, decide which to use:
1229        if maiden_name is not None:
1230            if married_name is not None:
1231                name = Name(married_name)
1232            else:
1233                name = Name(primary_name)
1234                surname_obj = name.get_primary_surname()
1235                surname_obj.set_surname(maiden_name)
1236        else:
1237            name = Name(primary_name)
1238        name.set_display_as(name_format)
1239        return _nd.display_name(name)
1240
1241    def display_attribute_header(self):
1242        """
1243        Display the attribute section and its table header
1244        """
1245        # begin attributes division and section title
1246        with Html("div", class_="subsection", id="attributes") as section:
1247            section += Html("h4", self._("Attributes"), inline=True)
1248
1249            # begin attributes table
1250            with Html("table", class_="infolist attrlist") as attrtable:
1251                section += attrtable
1252
1253                thead = Html("thead")
1254                attrtable += thead
1255
1256                trow = Html("tr")
1257                thead += trow
1258
1259                trow.extend(
1260                    Html("th", label, class_=colclass, inline=True)
1261                    for (label, colclass) in [
1262                        (self._("Type"), "ColumnType"),
1263                        (self._("Value"), "ColumnValue"),
1264                        (self._("Notes"), "ColumnNotes"),
1265                        (self._("Sources"), "ColumnSources")]
1266                )
1267        return section, attrtable
1268
1269    def display_attr_list(self, attrlist,
1270                          attrtable):
1271        """
1272        Will display a list of attributes
1273
1274        @param: attrlist  -- a list of attributes
1275        @param: attrtable -- the table element that is being added to
1276        """
1277        tbody = Html("tbody")
1278        attrtable += tbody
1279
1280        tbody.extend(
1281            self.dump_attribute(attr) for attr in attrlist
1282        )
1283
1284    def write_footer(self, date):
1285        """
1286        Will create and display the footer section of each page...
1287
1288        @param: bottom -- whether to specify location of footer section
1289                          or not?
1290        """
1291        # begin footer division
1292        with Html("div", id="footer") as footer:
1293
1294            footer_note = self.report.options['footernote']
1295            if footer_note:
1296                note = self.get_note_format(
1297                    self.r_db.get_note_from_gramps_id(footer_note),
1298                    False
1299                    )
1300                user_footer = Html("div", id='user_footer')
1301                footer += user_footer
1302
1303                # attach note
1304                user_footer += note
1305
1306            msg = self._('Generated by %(gramps_home_html_start)s'
1307                         'Gramps%(html_end)s %(version)s'
1308                        ) % {'gramps_home_html_start' :
1309                             '<a href="' + URL_HOMEPAGE + '">',
1310                             'html_end' : '</a>',
1311                             'version' : VERSION}
1312            if date is not None and date > 0:
1313                msg += "<br />"
1314                last_modif = datetime.datetime.fromtimestamp(date).strftime(
1315                    '%Y-%m-%d %H:%M:%S')
1316                msg += self._('Last change was the %(date)s') % {'date' :
1317                                                                 last_modif}
1318            else:
1319                dat_txt = self._(' on %(date)s')
1320                msg += dat_txt % {'date' : self.rlocale.get_date(Today())}
1321
1322            origin1 = self.report.filter.get_name(self.rlocale)
1323            filt_number = self.report.options['filter']
1324            # optional "link-home" feature; see bug report #2736
1325            if self.report.options['linkhome']:
1326                center_person = self.r_db.get_person_from_gramps_id(
1327                    self.report.options['pid'])
1328                if (center_person and
1329                        self.report.person_in_webreport(center_person.handle)):
1330                    center_person_url = self.report.build_url_fname_html(
1331                        center_person.handle, "ppl", self.uplink)
1332
1333                    #person_name = self.get_name(center_person)
1334                    if filt_number > 0 and  filt_number < 5:
1335                        subject_url = '<a href="' + center_person_url + '">'
1336                        subject_url += origin1 + '</a>'
1337                    else:
1338                        subject_url = origin1
1339                    msg += self._(
1340                        '%(http_break)sCreated for %(subject_url)s') % {
1341                            'http_break'  : '<br />',
1342                            'subject_url' : subject_url}
1343                else:
1344                    msg += self._(
1345                        '%(http_break)sCreated for %(subject_url)s') % {
1346                            'http_break'  : '<br />',
1347                            'subject_url' : origin1}
1348
1349            # creation author
1350            footer += Html("p", msg, id='createdate')
1351
1352            # get copyright license for all pages
1353            copy_nr = self.report.copyright
1354
1355            text = ''
1356            if copy_nr == 0:
1357                if self.author:
1358                    year = Today().get_year()
1359                    text = '&copy; %(year)d %(person)s' % {
1360                        'person' : self.author, 'year' : year}
1361            elif copy_nr < len(_CC):
1362                # Note. This is a URL
1363                fname = "/".join(["images", "somerights20.gif"])
1364                url = self.report.build_url_fname(fname, None, self.uplink)
1365                text = _CC[copy_nr] % {'gif_fname' : url}
1366            footer += Html("p", text, id='copyright')
1367
1368        # return footer to its callers
1369        return footer
1370
1371    def write_header(self, title):
1372        """
1373        Note. 'title' is used as currentsection in the navigation links and
1374        as part of the header title.
1375
1376        @param: title -- Is the title of the web page
1377        """
1378        # begin each html page...
1379        xmllang = xml_lang()
1380        page, head, body = Html.page('%s - %s' %
1381                                     (html_escape(self.title_str.strip()),
1382                                      html_escape(title)),
1383                                     self.report.encoding,
1384                                     xmllang, cms=self.usecms)
1385
1386        # temporary fix for .php parsing error
1387        if self.ext in [".php", ".php3", ".cgi"]:
1388            del page[0]
1389
1390        # Header constants
1391        _meta1 = 'name ="viewport" content="width=device-width; '
1392        _meta1 += 'height=device-height; initial-scale=1.0; '
1393        _meta1 += 'minimum-scale=0.5; maximum-scale=10.0; user-scalable=yes"'
1394        _meta2 = 'name ="apple-mobile-web-app-capable" content="yes"'
1395        _meta3 = 'name="generator" content="%s %s %s"' % (
1396            PROGRAM_NAME, VERSION, URL_HOMEPAGE)
1397        _meta4 = 'name="author" content="%s"' % self.author
1398
1399        # create additional meta tags
1400        meta = Html("meta", attr=_meta1) + (
1401            Html("meta", attr=_meta2, indent=False),
1402            Html("meta", attr=_meta3, indent=False),
1403            Html("meta", attr=_meta4, indent=False)
1404        )
1405
1406        # Link to _NARRATIVESCREEN  stylesheet
1407        fname = "/".join(["css", _NARRATIVESCREEN])
1408        url2 = self.report.build_url_fname(fname, None, self.uplink)
1409
1410        # Link to _NARRATIVEPRINT stylesheet
1411        fname = "/".join(["css", _NARRATIVEPRINT])
1412        url3 = self.report.build_url_fname(fname, None, self.uplink)
1413
1414        # Link to Gramps favicon
1415        fname = "/".join(['images', 'favicon2.ico'])
1416        url4 = self.report.build_url_image("favicon2.ico",
1417                                           "images", self.uplink)
1418
1419        # create stylesheet and favicon links
1420        links = Html("link", type="image/x-icon",
1421                     href=url4, rel="shortcut icon") + (
1422                         Html("link", type="text/css", href=url3,
1423                              media='print', rel="stylesheet", indent=False),
1424                         Html("link", type="text/css", href=url2,
1425                              media="screen", rel="stylesheet", indent=False),
1426                         )
1427
1428        # Link to Navigation Menus stylesheet
1429        if CSS[self.report.css]["navigation"]:
1430            fname = "/".join(["css", "narrative-menus.css"])
1431            url = self.report.build_url_fname(fname, None, self.uplink)
1432            links += Html("link", type="text/css", href=url,
1433                          media="screen", rel="stylesheet", indent=False)
1434
1435        # add additional meta and link tags
1436        head += meta
1437        head += links
1438
1439        # Add the script to control the menu
1440        menuscript = Html("<script>function navFunction() { "
1441                          "var x = document.getElementById(\"dropmenu\"); "
1442                          "if (x.className === \"nav\") { x.className += \""
1443                          " responsive\"; } else { x.className = \"nav\"; }"
1444                          " }</script>")
1445        head += menuscript
1446
1447        # add outerwrapper to set the overall page width
1448        outerwrapperdiv = Html("div", id='outerwrapper')
1449        body += outerwrapperdiv
1450
1451        # begin header section
1452        #headerdiv = Html("div", id='header') + (
1453        headerdiv = Html("div", id='header') + (
1454            Html("<a href=\"javascript:void(0);\" class=\"navIcon\""
1455                 " onclick=\"navFunction()\">&#8801;</a>")) + (
1456                     Html("h1", html_escape(self.title_str),
1457                          id="SiteTitle", inline=True)
1458                     )
1459        outerwrapperdiv += headerdiv
1460
1461        header_note = self.report.options['headernote']
1462        if header_note:
1463            note = self.get_note_format(
1464                self.r_db.get_note_from_gramps_id(header_note),
1465                False)
1466
1467            user_header = Html("div", id='user_header')
1468            headerdiv += user_header
1469
1470            # attach note
1471            user_header += note
1472
1473        # Begin Navigation Menu--
1474        # is the style sheet either Basic-Blue or Visually Impaired,
1475        # and menu layout is Drop Down?
1476        if (self.report.css == _("Basic-Blue") or
1477                self.report.css == _("Visually Impaired")
1478           ) and self.report.navigation == "dropdown":
1479            outerwrapperdiv += self.display_drop_menu()
1480        else:
1481            outerwrapperdiv += self.display_nav_links(title)
1482
1483        # message for Codacy :
1484        # body is used in some modules to add functions like onload(),
1485        # initialize(), ...
1486        # some modules doesn't need that, so body is an unused variable
1487        # in these modules.
1488        # return page, head, and body to its classes...
1489        return page, head, body, outerwrapperdiv
1490
1491    def display_nav_links(self, currentsection):
1492        """
1493        Creates the navigation menu
1494
1495        @param: currentsection = which menu item are you on
1496        """
1497        # include repositories or not?
1498        inc_repos = True
1499        if (not self.report.inc_repository or
1500                not self.r_db.get_repository_handles()):
1501            inc_repos = False
1502
1503        # create media pages...
1504        _create_media_link = False
1505        if self.create_media:
1506            _create_media_link = True
1507            if self.create_thumbs_only:
1508                _create_media_link = False
1509
1510        # create link to web calendar pages...
1511        #_create_calendar_link = False
1512        if self.usecal:
1513            #_create_calendar_link = True
1514            self.target_cal_uri += "/index"
1515
1516        # Determine which menu items will be available?
1517        # Menu items have been adjusted to concide with Gramps Navigation
1518        # Sidebar order...
1519
1520        navs = [
1521            (self.report.index_fname, self._("Html|Home"),
1522             self.report.use_home),
1523            (self.report.intro_fname, self._("Introduction"),
1524             self.report.use_intro),
1525            (self.report.extrapage, self.extrapagename, (self.extrapage != "")),
1526            ('individuals', self._("Individuals"), True),
1527            (self.report.surname_fname, self._("Surnames"), True),
1528            ('families', self._("Families"), self.report.inc_families),
1529            ('events', self._("Events"), self.report.inc_events),
1530            ('places', self._("Places"), self.report.inc_places),
1531            ('sources', self._("Sources"), self.report.inc_sources),
1532            ('repositories', self._("Repositories"), inc_repos),
1533            ('media', self._("Media"), _create_media_link),
1534            ('thumbnails', self._("Thumbnails"), self.create_media),
1535            ('download', self._("Download"), self.report.inc_download),
1536            ("addressbook", self._("Address Book"),
1537             self.report.inc_addressbook),
1538            ('contact', self._("Contact"), self.report.use_contact),
1539            ('statistics', self._("Statistics"), self.report.inc_stats),
1540            (self.target_cal_uri, self._("Web Calendar"), self.usecal)
1541        ]
1542
1543        # Remove menu sections if they are not being created?
1544        navs = ((url_text, nav_text)
1545                for url_text, nav_text, cond in navs if cond)
1546        menu_items = [[url, text] for url, text in navs]
1547        number_items = len(menu_items)
1548
1549        # begin navigation menu division...
1550        with Html("div", class_="wrappernav",
1551                  id="nav", role="navigation") as navigation:
1552            with Html("div", class_="container") as container:
1553
1554                index = 0
1555                unordered = Html("ul", class_="nav", id="dropmenu")
1556                while index < number_items:
1557                    url_fname, nav_text = menu_items[index]
1558                    hyper = self.get_nav_menu_hyperlink(url_fname, nav_text)
1559
1560                    # Define 'currentsection' to correctly set navlink item
1561                    # CSS id 'CurrentSection' for Navigation styling.
1562                    # Use 'self.report.cur_fname' to determine
1563                    # 'CurrentSection' for individual elements for
1564                    # Navigation styling.
1565
1566                    # Figure out if we need <li class = "CurrentSection">
1567                    # or just <li>
1568
1569                    check_cs = False
1570                    if nav_text == currentsection:
1571                        check_cs = True
1572                    elif nav_text == _("Surnames"):
1573                        if "srn" in self.report.cur_fname:
1574                            check_cs = True
1575                        elif _("Surnames") in currentsection:
1576                            check_cs = True
1577                    elif nav_text == _("Individuals"):
1578                        if "ppl" in self.report.cur_fname:
1579                            check_cs = True
1580                    elif nav_text == _("Families"):
1581                        if "fam" in self.report.cur_fname:
1582                            check_cs = True
1583                    elif nav_text == _("Sources"):
1584                        if "src" in self.report.cur_fname:
1585                            check_cs = True
1586                    elif nav_text == _("Places"):
1587                        if "plc" in self.report.cur_fname:
1588                            check_cs = True
1589                    elif nav_text == _("Events"):
1590                        if "evt" in self.report.cur_fname:
1591                            check_cs = True
1592                    elif nav_text == _("Media"):
1593                        if "img" in self.report.cur_fname:
1594                            check_cs = True
1595                    elif nav_text == _("Address Book"):
1596                        if "addr" in self.report.cur_fname:
1597                            check_cs = True
1598                    temp_cs = 'class = "CurrentSection"'
1599                    check_cs = temp_cs if check_cs else False
1600                    if check_cs:
1601                        unordered.extend(
1602                            Html("li", hyper, attr=check_cs, inline=True)
1603                        )
1604                    else:
1605                        unordered.extend(
1606                            Html("li", hyper, inline=True)
1607                        )
1608                    index += 1
1609
1610                if self.prevnext:
1611                    prv = Html('<a onclick="history.go(-1);">%s</a>' %
1612                               self._("Previous"))
1613                    nxt = Html('<a onclick="history.go(+1);">%s</a>' %
1614                               self._("Next"))
1615                    unordered.extend(Html("li", prv, inline=True))
1616                    unordered.extend(Html("li", nxt, inline=True))
1617                container += unordered
1618            navigation += container
1619        return navigation
1620
1621    def display_drop_menu(self):
1622        """
1623        Creates the Drop Down Navigation Menu
1624        """
1625        # include repositories or not?
1626        inc_repos = True
1627        if (not self.report.inc_repository or
1628                not self.r_db.get_repository_handles()):
1629            inc_repos = False
1630
1631        # create media pages...
1632        _create_media_link = False
1633        if self.create_media:
1634            _create_media_link = True
1635            if self.create_thumbs_only:
1636                _create_media_link = False
1637
1638        personal = [
1639            (self.report.intro_fname, self._("Introduction"),
1640             self.report.use_intro),
1641            ("individuals", self._("Individuals"), True),
1642            (self.report.surname_fname, self._("Surnames"), True),
1643            ("families", self._("Families"), self.report.inc_families)
1644        ]
1645        personal = ((url_text, nav_text)
1646                    for url_text, nav_text, cond in personal if cond)
1647        personal = [[url, text] for url, text in personal]
1648
1649        navs1 = [
1650            ("events", self._("Events"), self.report.inc_events),
1651            ("places", self._("Places"), True),
1652            ("sources", self._("Sources"), True),
1653            ("repositories", self._("Repositories"), inc_repos)
1654        ]
1655        navs1 = ((url_text, nav_text)
1656                 for url_text, nav_text, cond in navs1 if cond)
1657        navs1 = [[url, text] for url, text in navs1]
1658
1659        media = [
1660            ("media", self._("Media"), _create_media_link),
1661            ("thumbnails", self._("Thumbnails"), True)
1662        ]
1663        media = ((url_text, nav_text)
1664                 for url_text, nav_text, cond in media if cond)
1665        media = [[url, text] for url, text in media]
1666
1667        misc = [
1668            ('download', self._("Download"), self.report.inc_download),
1669            ("addressbook", self._("Address Book"), self.report.inc_addressbook)
1670        ]
1671        misc = ((url_text, nav_text)
1672                for url_text, nav_text, cond in misc if cond)
1673        misc = [[url, text] for url, text in misc]
1674
1675        contact = [
1676            ('contact', self._("Contact"), self.report.use_contact)
1677        ]
1678        contact = ((url_text, nav_text)
1679                   for url_text, nav_text, cond in contact if cond)
1680        contact = [[url, text] for url, text in contact]
1681
1682        # begin navigation menu division...
1683        with Html("div", class_="wrapper",
1684                  id="nav", role="navigation") as navigation:
1685            with Html("div", class_="container") as container:
1686                unordered = Html("ul", class_="menu", id="dropmenu")
1687
1688                if self.report.use_home:
1689                    list_html = Html("li",
1690                                     self.get_nav_menu_hyperlink(
1691                                         self.report.index_fname,
1692                                         self._("Html|Home")))
1693                    unordered += list_html
1694
1695                # add personal column
1696                self.get_column_data(unordered, personal, self._("Personal"))
1697
1698                if navs1:
1699                    for url_fname, nav_text in navs1:
1700                        unordered.extend(
1701                            Html("li", self.get_nav_menu_hyperlink(url_fname,
1702                                                                   nav_text),
1703                                 inline=True)
1704                        )
1705
1706                # add media column
1707                self.get_column_data(unordered, media, self._("Media"))
1708
1709                # add miscellaneous column
1710                self.get_column_data(unordered, misc, self._("Miscellaneous"))
1711
1712                # add contact column
1713                self.get_column_data(unordered, contact, _("Contact"))
1714
1715                container += unordered
1716            navigation += container
1717        return navigation
1718
1719    def add_image(self, option_name, head, height=0):
1720        """
1721        Will add an image (if present) to the page
1722        If this image contains regions, try to add them.
1723
1724        @param: option_name -- The name of the report option
1725        @param: height      -- Height of the image
1726        """
1727        pic_id = self.report.options[option_name]
1728        if pic_id:
1729            obj = self.r_db.get_media_from_gramps_id(pic_id)
1730            if obj is None:
1731                return None
1732            # get media rectangles
1733            _region_items = self.media_ref_rect_regions(obj.get_handle(),
1734                                                        linkurl=self.uplink)
1735
1736            # if there are media rectangle regions, attach behaviour style sheet
1737            if _region_items:
1738                fname = "/".join(["css", "behaviour.css"])
1739                url = self.report.build_url_fname(fname, None, self.uplink)
1740                head += Html("link", href=url, type="text/css",
1741                             media="screen", rel="stylesheet")
1742            mime_type = obj.get_mime_type()
1743            if mime_type and mime_type.startswith("image"):
1744                try:
1745
1746                    newpath, dummy_tpath = self.report.prepare_copy_media(obj)
1747                    self.report.copy_file(media_path_full(
1748                        self.r_db, obj.get_path()), newpath)
1749
1750                    # begin image
1751                    with Html("div", id="GalleryDisplay",
1752                              style='width: auto; height: auto') as image:
1753                        if _region_items:
1754                            # add all regions and links to persons
1755                            regions = Html("ol", class_="RegionBox")
1756                            while _region_items:
1757                                (name, coord_x, coord_y,
1758                                 width, height, linkurl
1759                                ) = _region_items.pop()
1760                                regions += Html(
1761                                    "li",
1762                                    style="left:%d%%; "
1763                                          "top:%d%%; "
1764                                          "width:%d%%; "
1765                                          "height:%d%%;" % (
1766                                              coord_x, coord_y,
1767                                              width, height)) + (
1768                                                  Html("a", name,
1769                                                       href=linkurl)
1770                                                  )
1771                            image += regions
1772
1773                        # add image
1774                        imag = Html("img")
1775                        imag.attr = ''
1776                        if height:
1777                            imag.attr += 'height = "%d"'  % height
1778
1779                        descr = html_escape(obj.get_description())
1780                        newpath = self.report.build_url_fname(newpath)
1781                        imag.attr += ' src = "%s" alt = "%s"' % (newpath, descr)
1782                        fname = self.report.build_url_fname(obj.get_handle(),
1783                                                            "img",
1784                                                            False) + self.ext
1785                        #image += imag
1786                        inc_gallery = self.report.options['gallery']
1787                        if not self.create_thumbs_only and inc_gallery:
1788                            img_link = Html("a", href=fname, title=descr) + (
1789                                Html("img", src=newpath, alt=descr))
1790                        else:
1791                            # We can't show the image html page.
1792                            # This page doesn't exist.
1793                            img_link = Html("img", src=newpath, alt=descr)
1794                        image += img_link
1795
1796                    return image
1797
1798                except (IOError, OSError) as msg:
1799                    self.r_user.warn(_("Could not add photo to page"),
1800                                     str(msg))
1801
1802        # no image to return
1803        return None
1804
1805    def media_ref_rect_regions(self, handle, linkurl=True):
1806        """
1807        Gramps feature #2634 -- attempt to highlight subregions in media
1808        objects and link back to the relevant web page.
1809
1810        This next section of code builds up the "records" we'll need to
1811        generate the html/css code to support the subregions
1812
1813        @param: handle -- The media handle to use
1814        """
1815        # get all of the backlinks to this media object; meaning all of
1816        # the people, events, places, etc..., that use this image
1817        _region_items = set()
1818        for (classname, newhandle) in self.r_db.find_backlink_handles(
1819                handle,
1820                include_classes=["Person", "Family", "Event", "Place"]):
1821
1822            # for each of the backlinks, get the relevant object from the db
1823            # and determine a few important things, such as a text name we
1824            # can use, and the URL to a relevant web page
1825            _obj = None
1826            _name = ""
1827            _linkurl = "#"
1828            if classname == "Person":
1829                # Is this a person for whom we have built a page:
1830                if self.report.person_in_webreport(newhandle):
1831                    # If so, let's add a link to them:
1832                    _obj = self.r_db.get_person_from_handle(newhandle)
1833                    if _obj:
1834                        # What is the shortest possible name we could use
1835                        # for this person?
1836                        _name = (_obj.get_primary_name().get_call_name() or
1837                                 _obj.get_primary_name().get_first_name() or
1838                                 self._("Unknown")
1839                                )
1840                        _linkurl = self.report.build_url_fname_html(_obj.handle,
1841                                                                    "ppl",
1842                                                                    linkurl)
1843            elif classname == "Family" and self.inc_families:
1844                _obj = self.r_db.get_family_from_handle(newhandle)
1845                partner1_handle = _obj.get_father_handle()
1846                partner2_handle = _obj.get_mother_handle()
1847                partner1 = None
1848                partner2 = None
1849                if partner1_handle:
1850                    partner1 = self.r_db.get_person_from_handle(
1851                        partner1_handle)
1852                if partner2_handle:
1853                    partner2 = self.r_db.get_person_from_handle(
1854                        partner2_handle)
1855                if partner2 and partner1:
1856                    _name = partner1.get_primary_name().get_first_name()
1857                    _linkurl = self.report.build_url_fname_html(partner1_handle,
1858                                                                "ppl", True)
1859                elif partner1:
1860                    _name = partner1.get_primary_name().get_first_name()
1861                    _linkurl = self.report.build_url_fname_html(partner1_handle,
1862                                                                "ppl", True)
1863                elif partner2:
1864                    _name = partner2.get_primary_name().get_first_name()
1865                    _linkurl = self.report.build_url_fname_html(partner2_handle,
1866                                                                "ppl", True)
1867                if not _name:
1868                    _name = self._("Unknown")
1869            elif classname == "Event" and self.inc_events:
1870                _obj = self.r_db.get_event_from_handle(newhandle)
1871                _name = _obj.get_description()
1872                if not _name:
1873                    _name = self._("Unknown")
1874                _linkurl = self.report.build_url_fname_html(_obj.handle,
1875                                                            "evt", True)
1876            elif classname == "Place":
1877                _obj = self.r_db.get_place_from_handle(newhandle)
1878                _name = _pd.display(self.r_db, _obj)
1879                if not _name:
1880                    _name = self._("Unknown")
1881                _linkurl = self.report.build_url_fname_html(newhandle,
1882                                                            "plc", True)
1883
1884            # continue looking through the loop for an object...
1885            if _obj is None:
1886                continue
1887
1888            # get a list of all media refs for this object
1889            media_list = _obj.get_media_list()
1890
1891            # go media refs looking for one that points to this image
1892            for mediaref in media_list:
1893
1894                # is this mediaref for this image?  do we have a rect?
1895                if mediaref.ref == handle and mediaref.rect is not None:
1896
1897                    (coord_x1, coord_y1, coord_x2, coord_y2) = mediaref.rect
1898                    # Gramps gives us absolute coordinates,
1899                    # but we need relative width + height
1900                    width = coord_x2 - coord_x1
1901                    height = coord_y2 - coord_y1
1902
1903                    # remember all this information, cause we'll need
1904                    # need it later when we output the <li>...</li> tags
1905                    item = (_name, coord_x1, coord_y1, width, height, _linkurl)
1906                    _region_items.add(item)
1907
1908        # End of code that looks for and prepares the media object regions
1909
1910        return sorted(_region_items)
1911
1912    def media_ref_region_to_object(self, media_handle, obj):
1913        """
1914        Return a region of this image if it refers to this object.
1915
1916        @param: media_handle -- The media handle to use
1917        @param: obj          -- The object reference
1918        """
1919        # get a list of all media refs for this object
1920        for mediaref in obj.get_media_list():
1921            # is this mediaref for this image?  do we have a rect?
1922            if (mediaref.ref == media_handle and
1923                    mediaref.rect is not None):
1924                return mediaref.rect # (x1, y1, x2, y2)
1925        return None
1926
1927    def disp_first_img_as_thumbnail(self, photolist, object_):
1928        """
1929        Return the Html of the first image of photolist that is
1930        associated with object. First image might be a region in an
1931        image. Or, the first image might have regions defined in it.
1932
1933        @param: photolist -- The list of media
1934        @param: object_   -- The object reference
1935        """
1936        if not photolist or not self.create_media:
1937            return None
1938
1939        photo_handle = photolist[0].get_reference_handle()
1940        photo = self.r_db.get_media_from_handle(photo_handle)
1941        mime_type = photo.get_mime_type()
1942        descr = photo.get_description()
1943
1944        # begin snapshot division
1945        with Html("div", class_="snapshot") as snapshot:
1946
1947            if mime_type:
1948
1949                region = self.media_ref_region_to_object(photo_handle, object_)
1950                if region:
1951
1952                    # make a thumbnail of this region
1953                    newpath = self.copy_thumbnail(photo_handle, photo, region)
1954                    newpath = self.report.build_url_fname(newpath, uplink=True)
1955                    snapshot += self.media_link(photo_handle, newpath, descr,
1956                                                uplink=self.uplink,
1957                                                usedescr=False)
1958                else:
1959                    dummy_rpath, newpath = self.report.prepare_copy_media(photo)
1960                    newpath = self.report.build_url_fname(newpath, uplink=True)
1961                    snapshot += self.media_link(photo_handle, newpath,
1962                                                descr,
1963                                                uplink=self.uplink,
1964                                                usedescr=False)
1965            else:
1966                # begin hyperlink
1967                snapshot += self.doc_link(photo_handle, descr,
1968                                          uplink=self.uplink, usedescr=False)
1969
1970        # return snapshot division to its callers
1971        return snapshot
1972
1973    def disp_add_img_as_gallery(self, photolist, object_):
1974        """
1975        Display additional image as gallery
1976
1977        @param: photolist -- The list of media
1978        @param: object_   -- The object reference
1979        """
1980        if not photolist or not self.create_media:
1981            return None
1982
1983        # make referenced images have the same order as in media list:
1984        photolist_handles = {}
1985        for mediaref in photolist:
1986            photolist_handles[mediaref.get_reference_handle()] = mediaref
1987        photolist_ordered = []
1988        for photoref in copy.copy(object_.get_media_list()):
1989            if photoref.ref in photolist_handles:
1990                photo = photolist_handles[photoref.ref]
1991                photolist_ordered.append(photo)
1992        # and add any that are left (should there be any?)
1993        photolist_ordered += photolist
1994
1995        # begin individualgallery division and section title
1996        with Html("div", class_="subsection", id="indivgallery") as section:
1997            section += Html("h4", self._("Media"), inline=True)
1998
1999            displayed = []
2000            for mediaref in photolist_ordered:
2001
2002                photo_handle = mediaref.get_reference_handle()
2003                photo = self.r_db.get_media_from_handle(photo_handle)
2004
2005                if photo_handle in displayed:
2006                    continue
2007                mime_type = photo.get_mime_type()
2008
2009                # get media description
2010                descr = photo.get_description()
2011
2012                if mime_type:
2013                    try:
2014                        # create thumbnail url
2015                        # extension needs to be added as it is not already there
2016                        url = self.report.build_url_fname(photo_handle, "thumb",
2017                                                          True) + ".png"
2018                        # begin hyperlink
2019                        section += self.media_link(photo_handle, url,
2020                                                   descr, uplink=self.uplink,
2021                                                   usedescr=True)
2022                    except (IOError, OSError) as msg:
2023                        self.r_user.warn(_("Could not add photo to page"),
2024                                         str(msg))
2025                else:
2026                    try:
2027                        # begin hyperlink
2028                        section += self.doc_link(photo_handle, descr,
2029                                                 uplink=self.uplink)
2030                    except (IOError, OSError) as msg:
2031                        self.r_user.warn(_("Could not add photo to page"),
2032                                         str(msg))
2033                displayed.append(photo_handle)
2034
2035        # add fullclear for proper styling
2036        section += FULLCLEAR
2037
2038        # return indivgallery division to its caller
2039        return section
2040
2041    def display_note_list(self, notelist=None):
2042        """
2043        Display note list
2044
2045        @param: notelist -- The list of notes
2046        """
2047        if not notelist:
2048            return None
2049
2050        # begin narrative division
2051        with Html("div", class_="subsection narrative") as section:
2052
2053            for notehandle in notelist:
2054                note = self.r_db.get_note_from_handle(notehandle)
2055
2056                if note:
2057                    note_text = self.get_note_format(note, True)
2058
2059                    # add section title
2060                    section += Html("h4", self._("Narrative"), inline=True)
2061
2062                    # attach note
2063                    section += note_text
2064
2065        # return notes to its callers
2066        return section
2067
2068    def display_url_list(self, urllist=None):
2069        """
2070        Display URL list
2071
2072        @param: urllist -- The list of urls
2073        """
2074        if not urllist:
2075            return None
2076
2077        # begin web links division
2078        with Html("div", class_="subsection", id="WebLinks") as section:
2079            section += Html("h4", self._("Web Links"), inline=True)
2080
2081            with Html("table", class_="infolist weblinks") as table:
2082                section += table
2083
2084                thead = Html("thead")
2085                table += thead
2086
2087                trow = Html("tr")
2088                thead += trow
2089
2090                trow.extend(Html('th', label, class_=colclass, inline=True)
2091                            for (label, colclass) in [
2092                                (self._("Type"), "ColumnType"),
2093                                (self._("Description"), "ColumnDescription")]
2094                           )
2095
2096                tbody = Html("tbody")
2097                table += tbody
2098
2099                for url in urllist:
2100
2101                    trow = Html("tr")
2102                    tbody += trow
2103
2104                    _type = self._(url.get_type().xml_str())
2105                    uri = url.get_path()
2106                    descr = url.get_description()
2107
2108                    # Email address
2109                    if _type == UrlType.EMAIL:
2110                        if not uri.startswith("mailto:"):
2111                            uri = "mailto:%(email)s" % {'email' : uri}
2112
2113                    # Web Site address
2114                    elif _type == UrlType.WEB_HOME:
2115                        if not (uri.startswith("http://") or
2116                                uri.startswith("https://")):
2117                            url = self.secure_mode
2118                            uri = url + "%(website)s" % {"website" : uri}
2119
2120                    # FTP server address
2121                    elif _type == UrlType.WEB_FTP:
2122                        if not (uri.startswith("ftp://") or
2123                                uri.startswith("ftps://")):
2124                            uri = "ftp://%(ftpsite)s" % {"ftpsite" : uri}
2125
2126                    descr = Html("p", html_escape(descr)) + (
2127                        Html("a", self._(" [Click to Go]"), href=uri, title=uri)
2128                    )
2129
2130                    trow.extend(
2131                        Html("td", data, class_=colclass, inline=True)
2132                        for (data, colclass) in [
2133                            (str(_type), "ColumnType"),
2134                            (descr, "ColumnDescription")
2135                        ]
2136                    )
2137        return section
2138
2139    def display_lds_ordinance(self, db_obj_):
2140        """
2141        Display LDS information for a person or family
2142
2143        @param: db_obj_ -- The database object
2144        """
2145        ldsordlist = db_obj_.lds_ord_list
2146        if not ldsordlist:
2147            return None
2148
2149        # begin LDS Ordinance division and section title
2150        with Html("div", class_="subsection", id="LDSOrdinance") as section:
2151            section += Html("h4", _("Latter-Day Saints/ LDS Ordinance"),
2152                            inline=True)
2153
2154            # ump individual LDS ordinance list
2155            section += self.dump_ordinance(db_obj_, "Person")
2156
2157        # return section to its caller
2158        return section
2159
2160    def display_ind_sources(self, srcobj):
2161        """
2162        Will create the "Source References" section for an object
2163
2164        @param: srcobj -- Sources object
2165        """
2166        list(map(
2167            lambda i: self.bibli.add_reference(
2168                self.r_db.get_citation_from_handle(i)),
2169            srcobj.get_citation_list()))
2170        sourcerefs = self.display_source_refs(self.bibli)
2171
2172        # return to its callers
2173        return sourcerefs
2174
2175    # Only used in IndividualPage.display_ind_sources(),
2176    # and MediaPage.display_media_sources()
2177    def display_source_refs(self, bibli):
2178        """
2179        Display source references
2180
2181        @param: bibli -- List of sources
2182        """
2183        if bibli.get_citation_count() == 0:
2184            return None
2185
2186        with Html("div", class_="subsection", id="sourcerefs") as section:
2187            section += Html("h4", self._("Source References"), inline=True)
2188
2189            ordered = Html("ol")
2190
2191            cindex = 0
2192            citationlist = bibli.get_citation_list()
2193            for citation in citationlist:
2194                cindex += 1
2195                # Add this source and its references to the page
2196                source = self.r_db.get_source_from_handle(
2197                    citation.get_source_handle())
2198                if source is not None:
2199                    if source.get_author():
2200                        authorstring = source.get_author() + ": "
2201                    else:
2202                        authorstring = ""
2203                    list_html = Html("li",
2204                                     self.source_link(
2205                                         source.get_handle(),
2206                                         authorstring + source.get_title(),
2207                                         source.get_gramps_id(), cindex,
2208                                         uplink=self.uplink))
2209                else:
2210                    list_html = Html("li", "None")
2211
2212                ordered1 = Html("ol")
2213                citation_ref_list = citation.get_ref_list()
2214                for key, sref in citation_ref_list:
2215                    cit_ref_li = Html("li", id="sref%d%s" % (cindex, key))
2216                    tmp = Html("ul")
2217                    conf = conf_strings.get(sref.confidence, self._('Unknown'))
2218                    if conf == conf_strings[Citation.CONF_NORMAL]:
2219                        conf = None
2220                    else:
2221                        conf = self._(conf)
2222                    for (label, data) in [[self._("Date"),
2223                                           self.rlocale.get_date(sref.date)],
2224                                          [self._("Page"), sref.page],
2225                                          [self._("Confidence"), conf]]:
2226                        if data:
2227                            tmp += Html("li",
2228                                        _("%(str1)s: %(str2)s") % {
2229                                            'str1' : label,
2230                                            'str2' : data
2231                                            })
2232                    if self.create_media:
2233                        for media_ref in sref.get_media_list():
2234                            media_handle = media_ref.get_reference_handle()
2235                            media = self.r_db.get_media_from_handle(
2236                                media_handle)
2237                            if media:
2238                                mime_type = media.get_mime_type()
2239                                if mime_type:
2240                                    if mime_type.startswith("image/"):
2241                                        real_path, new_path = \
2242                                            self.report.prepare_copy_media(
2243                                                media)
2244                                        newpath = self.report.build_url_fname(
2245                                            new_path, uplink=self.uplink)
2246                                        self.report.copy_file(
2247                                            media_path_full(self.r_db,
2248                                                            media.get_path()),
2249                                            real_path)
2250
2251                                        tmp += Html("li",
2252                                                    self.media_link(
2253                                                        media_handle,
2254                                                        newpath,
2255                                                        media.get_description(),
2256                                                        self.uplink,
2257                                                        usedescr=False),
2258                                                    inline=True)
2259
2260                                    else:
2261                                        tmp += Html("li",
2262                                                    self.doc_link(
2263                                                        media_handle,
2264                                                        media.get_description(),
2265                                                        self.uplink,
2266                                                        usedescr=False),
2267                                                    inline=True)
2268                    for handle in sref.get_note_list():
2269                        this_note = self.r_db.get_note_from_handle(handle)
2270                        if this_note is not None:
2271                            note_format = self.get_note_format(this_note, True)
2272                            tmp += Html("li",
2273                                        _("%(str1)s: %(str2)s") % {
2274                                            'str1' : str(this_note.get_type()),
2275                                            'str2' : note_format
2276                                            })
2277                    if tmp:
2278                        cit_ref_li += tmp
2279                        ordered1 += cit_ref_li
2280
2281                if citation_ref_list:
2282                    list_html += ordered1
2283                ordered += list_html
2284            section += ordered
2285
2286        # return section to its caller
2287        return section
2288
2289    def family_map_link(self, handle, url):
2290        """
2291        Creates a link to the family map
2292
2293        @param: handle -- The family handle
2294        @param: url    -- url to be linked
2295        """
2296        dummy_handle = handle
2297        return Html("a", self._("Family Map"), href=url,
2298                    title=self._("Family Map"), class_="familymap", inline=True)
2299
2300    def display_spouse(self, partner, family, place_lat_long):
2301        """
2302        Display an individual's partner
2303
2304        @param: partner        -- The partner
2305        @param: family         -- The family
2306        @param: place_lat_long -- For use in Family Map Pages. This will be
2307                                  None if called from Family pages, which do
2308                                  not create a Family Map
2309        """
2310        gender = partner.get_gender()
2311        reltype = family.get_relationship()
2312
2313        rtype = self._(str(family.get_relationship().xml_str()))
2314
2315        if reltype == FamilyRelType.MARRIED:
2316            if gender == Person.FEMALE:
2317                relstr = self._("Wife")
2318            elif gender == Person.MALE:
2319                relstr = self._("Husband")
2320            else:
2321                relstr = self._("Partner")
2322        else:
2323            relstr = self._("Partner")
2324
2325        # display family relationship status, and add spouse to FamilyMapPages
2326        if self.familymappages:
2327            self._get_event_place(partner, place_lat_long)
2328
2329        trow = Html("tr", class_="BeginFamily") + (
2330            Html("td", rtype, class_="ColumnType", inline=True),
2331            Html("td", relstr, class_="ColumnAttribute", inline=True)
2332        )
2333
2334        tcell = Html("td", class_="ColumnValue")
2335        trow += tcell
2336
2337        tcell += self.new_person_link(partner.get_handle(), uplink=True,
2338                                      person=partner)
2339        birth = death = ""
2340        bd_event = get_birth_or_fallback(self.r_db, partner)
2341        if bd_event:
2342            birth = self.rlocale.get_date(bd_event.get_date_object())
2343        dd_event = get_death_or_fallback(self.r_db, partner)
2344        if dd_event:
2345            death = self.rlocale.get_date(dd_event.get_date_object())
2346
2347        if death == "":
2348            death = "..."
2349        tcell += " ( * ", birth, " + ", death, " )"
2350
2351        return trow
2352
2353    def display_child_link(self, chandle):
2354        """
2355        display child link ...
2356
2357        @param: chandle -- Child handle
2358        """
2359        return self.new_person_link(chandle, uplink=True)
2360
2361    def new_person_link(self, person_handle, uplink=False, person=None,
2362                        name_style=_NAME_STYLE_DEFAULT):
2363        """
2364        creates a link for a person. If a page is generated for the person, a
2365        hyperlink is created, else just the name of the person. The returned
2366        vale will be an Html object if a hyperlink is generated, otherwise
2367        just a string
2368
2369        @param: person_handle -- Person in database
2370        @param: uplink        -- If True, then "../../../" is inserted in
2371                                 front of the result
2372        @param: person        -- Person object. This does not need to be
2373                                 passed. It should be passed if the person
2374                                 object has already been retrieved, as it
2375                                 will be used to improve performance
2376        """
2377        result = self.report.obj_dict.get(Person).get(person_handle)
2378
2379        # construct link, name and gid
2380        if result is None:
2381            # The person is not included in the webreport
2382            link = ""
2383            if person is None:
2384                person = self.r_db.get_person_from_handle(person_handle)
2385            if person:
2386                name = self.report.get_person_name(person)
2387                gid = person.get_gramps_id()
2388            else:
2389                name = _("Unknown")
2390                gid = ""
2391        else:
2392            # The person has been encountered in the web report, but this does
2393            # not necessarily mean that a page has been generated
2394            (link, name, gid) = result
2395
2396        name = html_escape(name)
2397        # construct the result
2398        if not self.noid and gid != "":
2399            gid_html = Html("span", " [%s]" % gid, class_="grampsid",
2400                            inline=True)
2401        else:
2402            gid_html = ""
2403
2404        if link != "":
2405            url = self.report.build_url_fname(link, uplink=uplink)
2406            hyper = Html("a", name, gid_html, href=url, inline=True)
2407        else:
2408            hyper = name + str(gid_html)
2409
2410        return hyper
2411
2412    def media_link(self, media_handle, img_url, name,
2413                   uplink=False, usedescr=True):
2414        """
2415        creates and returns a hyperlink to the thumbnail image
2416
2417        @param: media_handle -- Photo handle from report database
2418        @param: img_url      -- Thumbnail url
2419        @param: name         -- Photo description
2420        @param: uplink       -- If True, then "../../../" is inserted in front
2421                                of the result.
2422        @param: usedescr     -- Add media description
2423        """
2424        url = self.report.build_url_fname_html(media_handle, "img", uplink)
2425        name = html_escape(name)
2426
2427        # begin thumbnail division
2428        with Html("div", class_="thumbnail") as thumbnail:
2429
2430            # begin hyperlink
2431            if not self.create_thumbs_only:
2432                hyper = Html("a", href=url, title=name) + (
2433                    Html("img", src=img_url, alt=name)
2434                )
2435            else:
2436                hyper = Html("img", src=img_url, alt=name)
2437            thumbnail += hyper
2438
2439            if usedescr:
2440                hyper += Html("p", name, inline=True)
2441        return thumbnail
2442
2443    def doc_link(self, handle, name, uplink=False, usedescr=True):
2444        """
2445        create a hyperlink for the media object and returns it
2446
2447        @param: handle   -- Document handle
2448        @param: name     -- Document name
2449        @param: uplink   -- If True, then "../../../" is inserted in front of
2450                            the result.
2451        @param: usedescr -- Add description to hyperlink
2452        """
2453        url = self.report.build_url_fname_html(handle, "img", uplink)
2454        name = html_escape(name)
2455
2456        # begin thumbnail division
2457        with Html("div", class_="thumbnail") as thumbnail:
2458            document_url = self.report.build_url_image("document.png",
2459                                                       "images", uplink)
2460
2461            if not self.create_thumbs_only:
2462                document_link = Html("a", href=url, title=name) + (
2463                    Html("img", src=document_url, alt=name)
2464                )
2465            else:
2466                document_link = Html("img", src=document_url, alt=name)
2467
2468            if usedescr:
2469                document_link += Html('br') + (
2470                    Html("span", name, inline=True)
2471                )
2472            thumbnail += document_link
2473        return thumbnail
2474
2475    def place_link(self, handle, name, gid=None, uplink=False):
2476        """
2477        Returns a hyperlink for place link
2478
2479        @param: handle -- repository handle from report database
2480        @param: name   -- repository title
2481        @param: gid    -- gramps id
2482        @param: uplink -- If True, then "../../../" is inserted in front of
2483                          the result.
2484        """
2485        url = self.report.build_url_fname_html(handle, "plc", uplink)
2486
2487        hyper = Html("a", html_escape(name), href=url,
2488                     title=html_escape(name))
2489        if not self.noid and gid:
2490            hyper += Html("span", " [%s]" % gid, class_="grampsid", inline=True)
2491
2492        # return hyperlink to its callers
2493        return hyper
2494
2495    def dump_place(self, place, table):
2496        """
2497        Dump a place's information from within the database
2498
2499        @param: place -- Place object from the database
2500        @param: table -- Table from Placedetail
2501        """
2502        if place in self.report.visited:
2503            return None
2504        self.report.visited.append(place)
2505        # add table body
2506        tbody = Html("tbody")
2507        table += tbody
2508
2509        gid = place.gramps_id
2510        if not self.noid and gid:
2511            trow = Html("tr") + (
2512                Html("td", self._("Gramps ID"), class_="ColumnAttribute",
2513                     inline=True),
2514                Html("td", gid, class_="ColumnValue", inline=True)
2515            )
2516            tbody += trow
2517
2518        data = place.get_latitude()
2519        if data != "":
2520            trow = Html('tr') + (
2521                Html("td", self._("Latitude"), class_="ColumnAttribute",
2522                     inline=True),
2523                Html("td", data, class_="ColumnValue", inline=True)
2524            )
2525            tbody += trow
2526        data = place.get_longitude()
2527        if data != "":
2528            trow = Html('tr') + (
2529                Html("td", self._("Longitude"), class_="ColumnAttribute",
2530                     inline=True),
2531                Html("td", data, class_="ColumnValue", inline=True)
2532            )
2533            tbody += trow
2534
2535        mlocation = get_main_location(self.r_db, place)
2536        for (label, data) in [
2537                (self._("Street"), mlocation.get(PlaceType.STREET, '')),
2538                (self._("Locality"), mlocation.get(PlaceType.LOCALITY, '')),
2539                (self._("City"), mlocation.get(PlaceType.CITY, '')),
2540                (self._("Church Parish"),
2541                 mlocation.get(PlaceType.PARISH, '')),
2542                (self._("County"), mlocation.get(PlaceType.COUNTY, '')),
2543                (self._("State/ Province"),
2544                 mlocation.get(PlaceType.STATE, '')),
2545                (self._("Postal Code"), place.get_code()),
2546                (self._("Province"), mlocation.get(PlaceType.PROVINCE, '')),
2547                (self._("Country"), mlocation.get(PlaceType.COUNTRY, ''))]:
2548            if data:
2549                trow = Html("tr") + (
2550                    Html("td", label, class_="ColumnAttribute", inline=True),
2551                    Html("td", data, class_="ColumnValue", inline=True)
2552                )
2553                tbody += trow
2554
2555        # display all related locations
2556        for placeref in place.get_placeref_list():
2557            place_date = self.rlocale.get_date(placeref.get_date_object())
2558            if place_date != "":
2559                parent_place = self.r_db.get_place_from_handle(placeref.ref)
2560                parent_name = parent_place.get_name().get_value()
2561                trow = Html('tr') + (
2562                    Html("td", self._("Locations"), class_="ColumnAttribute",
2563                         inline=True),
2564                    Html("td", parent_name, class_="ColumnValue", inline=True),
2565                    Html("td", place_date, class_="ColumnValue", inline=True)
2566                )
2567                tbody += trow
2568
2569        altloc = place.get_alternative_names()
2570        if altloc:
2571            tbody += Html("tr") + Html("td", "&nbsp;", colspan=2)
2572            date_msg = self._("Date range in which the name is valid.")
2573            trow = Html("tr") + (
2574                Html("th", self._("Alternate Names"), colspan=1,
2575                     class_="ColumnAttribute", inline=True),
2576                Html("th", self._("Language"), colspan=1,
2577                     class_="ColumnAttribute", inline=True),
2578                Html("th", date_msg, colspan=1,
2579                     class_="ColumnAttribute", inline=True),
2580            )
2581            tbody += trow
2582            for loc in altloc:
2583                place_date = self.rlocale.get_date(loc.date)
2584                trow = Html("tr") + (
2585                    Html("td", loc.get_value(), class_="ColumnValue",
2586                         inline=True),
2587                    Html("td", loc.get_language(), class_="ColumnValue",
2588                         inline=True),
2589                    Html("td", place_date, class_="ColumnValue",
2590                         inline=True),
2591                )
2592                tbody += trow
2593
2594        altloc = place.get_alternate_locations()
2595        if altloc:
2596            tbody += Html("tr") + Html("td", "&nbsp;", colspan=2)
2597            trow = Html("tr") + (
2598                Html("th", self._("Alternate Locations"), colspan=2,
2599                     class_="ColumnAttribute", inline=True),
2600            )
2601            tbody += trow
2602            for loc in (nonempt
2603                        for nonempt in altloc if not nonempt.is_empty()):
2604                for (label, data) in [(self._("Street"), loc.street),
2605                                      (self._("Locality"), loc.locality),
2606                                      (self._("City"), loc.city),
2607                                      (self._("Church Parish"), loc.parish),
2608                                      (self._("County"), loc.county),
2609                                      (self._("State/ Province"), loc.state),
2610                                      (self._("Postal Code"), loc.postal),
2611                                      (self._("Country"), loc.country),]:
2612                    if data:
2613                        trow = Html("tr") + (
2614                            Html("td", label, class_="ColumnAttribute",
2615                                 inline=True),
2616                            Html("td", data, class_="ColumnValue", inline=True)
2617                        )
2618                        tbody += trow
2619                tbody += Html("tr") + Html("td", "&nbsp;", colspan=2)
2620
2621            # encloses
2622            with Html("div", class_='subsection encloses') as encloses:
2623                tbody += encloses
2624                encloses += Html("h4", self._("Place Encloses"), inline=True)
2625                with Html("table", class_="infolist place") as table:
2626                    encloses += table
2627                    visited = [place.handle]
2628                    for link in self.r_db.find_backlink_handles(
2629                            place.handle, include_classes=['Place']):
2630                        if link[1] in visited:
2631                            continue
2632                        visited.append(link[1])
2633                        c_place = self.r_db.get_place_from_handle(link[1])
2634                        placeref = None
2635                        for placeref in c_place.get_placeref_list():
2636                            if placeref.ref == place.handle:
2637                                gpfh = self.r_db.get_place_from_handle
2638                                eplace = gpfh(placeref.ref)
2639                                if not eplace:
2640                                    continue
2641                                place_name = c_place.get_name().get_value()
2642                                table += Html("tr") + Html("td", place_name)
2643
2644            # enclosed by
2645            with Html("div", class_='subsection encloses') as encloses:
2646                tbody += encloses
2647                encloses += Html("h4", self._("Enclosed By"), inline=True)
2648                with Html("table", class_="infolist place") as table:
2649                    encloses += table
2650                    visited = [place.handle]
2651                    placeref = None
2652                    for placeref in place.get_placeref_list():
2653                        if placeref.ref in visited:
2654                            continue
2655                        visited.append(placeref.ref)
2656                        pplace = self.r_db.get_place_from_handle(placeref.ref)
2657                        if not pplace:
2658                            continue
2659                        place_name = pplace.get_name().get_value()
2660                        table += Html("tr") + Html("td", place_name)
2661
2662        # enclosed by
2663        tbody += Html("tr") + Html("td", "&nbsp;")
2664        trow = Html("tr") + (
2665            Html("th", self._("Enclosed By"),
2666                 class_="ColumnAttribute", inline=True),
2667        )
2668        tbody += trow
2669        for placeref in place.get_placeref_list():
2670            parent_place = self.r_db.get_place_from_handle(placeref.ref)
2671            if parent_place:
2672                place_name = parent_place.get_name().get_value()
2673                if parent_place.handle in self.report.obj_dict[Place]:
2674                    place_hyper = self.place_link(parent_place.handle,
2675                                                  place_name,
2676                                                  uplink=self.uplink)
2677                else:
2678                    place_hyper = place_name
2679                trow = Html("tr") + (
2680                    Html("td", place_hyper, class_="ColumnValue",
2681                         inline=True))
2682                tbody += trow
2683
2684        # enclose
2685        tbody += Html("tr") + Html("td", "&nbsp;")
2686        trow = Html("tr") + (
2687            Html("th", self._("Place Encloses"),
2688                 class_="ColumnAttribute", inline=True),
2689        )
2690        tbody += trow
2691        for link in self.r_db.find_backlink_handles(
2692                place.handle, include_classes=['Place']):
2693            child_place = self.r_db.get_place_from_handle(link[1])
2694            placeref = None
2695            for placeref in child_place.get_placeref_list():
2696                if placeref.ref == place.handle:
2697                    place_name = child_place.get_name().get_value()
2698                    if child_place.handle in self.report.obj_dict[Place]:
2699                        place_hyper = self.place_link(child_place.handle,
2700                                                      place_name,
2701                                                      uplink=self.uplink)
2702                    else:
2703                        place_hyper = place_name
2704                    trow = Html("tr") + (
2705                        Html("td", place_hyper,
2706                             class_="ColumnValue", inline=True))
2707            tbody += trow
2708
2709        # return place table to its callers
2710        return table
2711
2712    def repository_link(self, repository_handle, name,
2713                        gid=None, uplink=False):
2714        """
2715        Returns a hyperlink for repository links
2716
2717        @param: repository_handle -- repository handle from report database
2718        @param: name              -- repository title
2719        @param: gid               -- gramps id
2720        @param: uplink            -- If True, then "../../../" is inserted in
2721                                     front of the result.
2722        """
2723        url = self.report.build_url_fname_html(repository_handle,
2724                                               'repo', uplink)
2725        name = html_escape(name)
2726
2727        hyper = Html("a", name, href=url, title=name)
2728
2729        if not self.noid and gid:
2730            hyper += Html("span", '[%s]' % gid, class_="grampsid", inline=True)
2731        return hyper
2732
2733    def dump_repository_ref_list(self, repo_ref_list):
2734        """
2735        Dumps the repository
2736
2737        @param: repo_ref_list -- The list of repositories references
2738        """
2739        if not repo_ref_list:
2740            return None
2741        # Repository list division...
2742        with Html("div", class_="subsection",
2743                  id="repositories") as repositories:
2744            repositories += Html("h4", self._("Repositories"), inline=True)
2745
2746            with Html("table", class_="infolist") as table:
2747                repositories += table
2748
2749                thead = Html("thead")
2750                table += thead
2751
2752                trow = Html("tr") + (
2753                    Html("th", self._("Number"), class_="ColumnRowLabel",
2754                         inline=True),
2755                    Html("th", self._("Title"), class_="ColumnName",
2756                         inline=True),
2757                    Html("th", self._("Type"), class_="ColumnName",
2758                         inline=True),
2759                    Html("th", self._("Call number"), class_="ColumnName",
2760                         inline=True)
2761                )
2762                thead += trow
2763
2764                tbody = Html("tbody")
2765                table += tbody
2766
2767                index = 1
2768                for repo_ref in repo_ref_list:
2769                    repo = self.r_db.get_repository_from_handle(repo_ref.ref)
2770                    if repo:
2771                        trow = Html("tr") + (
2772                            Html("td", index, class_="ColumnRowLabel",
2773                                 inline=True),
2774                            Html("td",
2775                                 self.repository_link(repo_ref.ref,
2776                                                      repo.get_name(),
2777                                                      repo.get_gramps_id(),
2778                                                      self.uplink)),
2779                            Html("td",
2780                                 self._(repo_ref.get_media_type().xml_str()),
2781                                 class_="ColumnName"),
2782                            Html("td", repo_ref.get_call_number(),
2783                                 class_="ColumnName")
2784                        )
2785                        tbody += trow
2786                        index += 1
2787        return repositories
2788
2789    def dump_residence(self, has_res):
2790        """
2791        Creates a residence from the database
2792
2793        @param: has_res -- The residence to use
2794        """
2795        if not has_res:
2796            return None
2797
2798        # begin residence division
2799        with Html("div", class_="content Residence") as residence:
2800            residence += Html("h4", self._("Residence"), inline=True)
2801
2802            with Html("table", class_="infolist place") as table:
2803                residence += table
2804
2805                place_handle = has_res.get_place_handle()
2806                if place_handle:
2807                    place = self.r_db.get_place_from_handle(place_handle)
2808                    if place:
2809                        self.dump_place(place, table)
2810
2811                descr = has_res.get_description()
2812                if descr:
2813
2814                    trow = Html("tr")
2815                    if len(table) == 3:
2816                        # append description row to tbody element
2817                        # of dump_place
2818                        table[-2] += trow
2819                    else:
2820                        # append description row to table element
2821                        table += trow
2822
2823                    trow.extend(Html("td", self._("Description"),
2824                                     class_="ColumnAttribute", inline=True))
2825                    trow.extend(Html("td", descr, class_="ColumnValue",
2826                                     inline=True))
2827
2828        # return information to its callers
2829        return residence
2830
2831    def display_bkref(self, bkref_list, depth):
2832        """
2833        Display a reference list for an object class
2834
2835        @param: bkref_list -- The reference list
2836        @param: depth      -- The style of list to use
2837        """
2838        list_style = "1", "a", "I", "A", "i"
2839        ordered = Html("ol", class_="Col1", role="Volume-n-Page")
2840        ordered.attr += " type=%s" % list_style[depth]
2841        if depth > len(list_style):
2842            return ""
2843        # Sort by the role of the object at the bkref_class, bkref_handle
2844        def sort_by_role(obj):
2845            """
2846            Sort by role
2847            """
2848            if obj[2] == "Primary":
2849                role = "0"
2850            elif obj[2] == "Family":
2851                role = "1"
2852            else:
2853                if self.reference_sort:
2854                    role = obj[2] # name
2855                elif len(obj[2].split('-')) > 1:
2856                    dummy_cal, role = obj[2].split(':') # date in ISO format
2857                    # dummy_cal is the original calendar. remove it.
2858                    if len(role.split(' ')) == 2:
2859                        # for sort, remove the modifier before, after...
2860                        (dummy_modifier, role) = role.split(' ')
2861                else:
2862                    role = "3"
2863            return role
2864
2865        for (bkref_class, bkref_handle, role) in sorted(
2866                bkref_list, key=lambda x:
2867                sort_by_role(x)):
2868            list_html = Html("li")
2869            path = self.report.obj_dict[bkref_class][bkref_handle][0]
2870            name = self.report.obj_dict[bkref_class][bkref_handle][1]
2871            gid = self.report.obj_dict[bkref_class][bkref_handle][2]
2872            if role != "":
2873                if self.reference_sort:
2874                    role = ""
2875                elif role[1:2] == ':':
2876                    # cal is the original calendar
2877                    cal, role = role.split(':')
2878                    # conver ISO date to Date for translation.
2879                    # all modifiers are in english, so convert them
2880                    # to the local language
2881                    if len(role.split(' - ')) > 1:
2882                        (date1, date2) = role.split(' - ')
2883                        role = self._("between") + " " + date1 + " "
2884                        role += self._("and") + " " + date2
2885                    elif len(role.split(' ')) == 2:
2886                        (pref, date) = role.split(' ')
2887                        if "aft" in pref:
2888                            role = self._("after") + " " + date
2889                        if "bef" in pref:
2890                            role = self._("before") + " " + date
2891                        if pref in ("abt", "about"):
2892                            role = self._("about") + " " + date
2893                        if "c" in pref:
2894                            role = self._("circa") + " " + date
2895                        if "around" in pref:
2896                            role = self._("around") + " " + date
2897                    # parse is done in the default language
2898                    date = _dp.parse(role)
2899                    # reset the date to the original calendar
2900                    cdate = date.to_calendar(Date.calendar_names[int(cal)])
2901                    ldate = self.rlocale.get_date(cdate)
2902                    role = " (%s) " % ldate
2903                else:
2904                    role = " (%s) " % self._(role)
2905            ordered += list_html
2906            if path == "":
2907                list_html += name
2908                list_html += self.display_bkref(
2909                    self.report.bkref_dict[bkref_class][bkref_handle],
2910                    depth+1)
2911            else:
2912                url = self.report.build_url_fname(path, uplink=self.uplink)
2913                if not self.noid and gid != "":
2914                    gid_html = Html("span", " [%s]" % gid,
2915                                    class_="grampsid", inline=True)
2916                else:
2917                    gid_html = ""
2918                list_html += Html("a", href=url) + name + role + gid_html
2919        return ordered
2920
2921    def display_bkref_list(self, obj_class, obj_handle):
2922        """
2923        Display a reference list for an object class
2924
2925        @param: obj_class  -- The object class to use
2926        @param: obj_handle -- The handle to use
2927        """
2928        bkref_list = self.report.bkref_dict[obj_class][obj_handle]
2929        if not bkref_list:
2930            return None
2931        # begin references division and title
2932        with Html("div", class_="subsection", id="references") as section:
2933            section += Html("h4", self._("References"), inline=True)
2934            depth = 0
2935            ordered = self.display_bkref(bkref_list, depth)
2936            section += ordered
2937        return section
2938
2939    # -----------------------------------------------------------------------
2940    #              # Web Page Fortmatter and writer
2941    # -----------------------------------------------------------------------
2942    def xhtml_writer(self, htmlinstance, output_file, sio, date):
2943        """
2944        Will format, write, and close the file
2945
2946        @param: output_file  -- Open file that is being written to
2947        @param: htmlinstance -- Web page created with libhtml
2948                                src/plugins/lib/libhtml.py
2949        """
2950        htmlinstance.write(partial(print, file=output_file))
2951
2952        # closes the file
2953        self.report.close_file(output_file, sio, date)
2954
2955