class FanChart(Report): def __init__(self, database, options, user): """ Create the FanChart object that produces the report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User instance This report needs the following parameters (class variables) that come in the options class. maxgen - Maximum number of generations to include. circle - Draw a full circle, half circle, or quarter circle. background - Background color is generation dependent or white. radial - Print radial texts roundabout or as upright as possible. draw_empty - draw background when there is no information same_style - use the same style for all generation incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) menu = options.menu self.set_locale(options.menu.get_option_by_name('trans').get_value()) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, self._locale) self.database = CacheProxyDb(self.database) self.max_generations = menu.get_option_by_name('maxgen').get_value() self.circle = menu.get_option_by_name('circle').get_value() self.background = menu.get_option_by_name('background').get_value() self.radial = menu.get_option_by_name('radial').get_value() pid = menu.get_option_by_name('pid').get_value() self.draw_empty = menu.get_option_by_name('draw_empty').get_value() self.same_style = menu.get_option_by_name('same_style').get_value() self.center_person = self.database.get_person_from_gramps_id(pid) if self.center_person is None: raise ReportError(_("Person %s is not in the Database") % pid) self.graphic_style = [] self.text_style = [] for i in range(0, self.max_generations): self.graphic_style.append('FC-Graphic' + '%02d' % i) self.text_style.append('FC-Text' + '%02d' % i) self.calendar = 0 self.height = 0 self.map = [None] * 2**self.max_generations self.text = {} def apply_filter(self, person_handle, index): """traverse the ancestors recursively until either the end of a line is found, or until we reach the maximum number of generations that we want to deal with""" if (not person_handle) or (index >= 2**self.max_generations): return self.map[index-1] = person_handle self.text[index-1] = self.get_info(person_handle, log2(index)) person = self.database.get_person_from_handle(person_handle) family_handle = person.get_main_parents_family_handle() if family_handle: family = self.database.get_family_from_handle(family_handle) self.apply_filter(family.get_father_handle(), index*2) self.apply_filter(family.get_mother_handle(), (index*2)+1) def write_report(self): self.doc.start_page() self.apply_filter(self.center_person.get_handle(), 1) p_rn = self.center_person.get_primary_name().get_regular_name() if self.circle == FULL_CIRCLE: max_angle = 360.0 start_angle = 90 max_circular = 5 _x_ = self.doc.get_usable_width() / 2.0 _y_ = self.doc.get_usable_height() / 2.0 min_xy = min(_x_, _y_) elif self.circle == HALF_CIRCLE: max_angle = 180.0 start_angle = 180 max_circular = 3 _x_ = (self.doc.get_usable_width()/2.0) _y_ = self.doc.get_usable_height() min_xy = min(_x_, _y_) else: # quarter circle max_angle = 90.0 start_angle = 270 max_circular = 2 _x_ = 0 _y_ = self.doc.get_usable_height() min_xy = min(self.doc.get_usable_width(), _y_) # choose one line or two lines translation according to the width title = self._("%(generations)d Generation Fan Chart " "for %(person)s") % { 'generations' : self.max_generations, 'person' : p_rn} title_nb_lines = 1 style_sheet = self.doc.get_style_sheet() if style_sheet: p_style = style_sheet.get_paragraph_style('FC-Title') if p_style: font = p_style.get_font() if font: title_width = utils.pt2cm(self.doc.string_width(font, title)) if title_width > self.doc.get_usable_width(): title = self._( "%(generations)d Generation Fan Chart " "for\n%(person)s") % { 'generations' : self.max_generations, 'person' : p_rn} title_nb_lines = 2 if self.circle == FULL_CIRCLE or self.circle == QUAR_CIRCLE: # adjust only if full circle or 1/4 circle in landscape mode if self.doc.get_usable_height() <= self.doc.get_usable_width(): # Should be in Landscape now style_sheet = self.doc.get_style_sheet() p_style = style_sheet.get_paragraph_style('FC-Title') if p_style: font = p_style.get_font() if font: fontsize = utils.pt2cm(font.get_size()) # _y_ is vertical distance to center of circle, # move center down 1 fontsize _y_ += fontsize*title_nb_lines # min_XY is the diameter of the circle, # subtract two fontsize # so we dont draw outside bottom of the paper min_xy = min(min_xy, _y_ - 2*fontsize*title_nb_lines) if self.max_generations > max_circular: block_size = min_xy / (self.max_generations * 2 - max_circular) else: block_size = min_xy / self.max_generations # adaptation of the fonts (title and others) optimized_style_sheet = self.get_optimized_style_sheet( title, max_circular, block_size, self.same_style, not self.same_style, # if same_style, use default generated colors self.background == BACKGROUND_WHITE) if optimized_style_sheet: self.doc.set_style_sheet(optimized_style_sheet) # title mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.center_text('FC-Graphic-title', title, self.doc.get_usable_width() / 2, 0, mark) # wheel for generation in range(0, min(max_circular, self.max_generations)): self.draw_circular(_x_, _y_, start_angle, max_angle, block_size, generation) for generation in range(max_circular, self.max_generations): self.draw_radial(_x_, _y_, start_angle, max_angle, block_size, generation) self.doc.end_page() def get_info(self, person_handle, generation): """ get info about a person """ person = self.database.get_person_from_handle(person_handle) p_pn = person.get_primary_name() self.calendar = config.get('preferences.calendar-format-report') birth = get_birth_or_fallback(self.database, person) bth = "" if birth: bth = birth.get_date_object() bth = str(bth.to_calendar(self.calendar).get_year()) if bth == 0: bth = "" elif birth.get_type() != EventType.BIRTH: bth += '*' death = get_death_or_fallback(self.database, person) dth = "" if death: dth = death.get_date_object() dth = str(dth.to_calendar(self.calendar).get_year()) if dth == 0: dth = "" elif death.get_type() != EventType.DEATH: dth += '*' if bth and dth: val = "%s - %s" % (str(bth), str(dth)) elif bth: val = "* %s" % (str(bth)) elif dth: val = "+ %s" % (str(dth)) else: val = "" if generation > 7: if (p_pn.get_first_name() != "") and (p_pn.get_surname() != ""): name = p_pn.get_first_name() + " " + p_pn.get_surname() else: name = p_pn.get_first_name() + p_pn.get_surname() if (name != "") and (val != ""): string = name + ", " + val else: string = name + val return [string] elif generation == 7: if (p_pn.get_first_name() != "") and (p_pn.get_surname() != ""): name = p_pn.get_first_name() + " " + p_pn.get_surname() else: name = p_pn.get_first_name() + p_pn.get_surname() if self.circle == FULL_CIRCLE: return [name, val] elif self.circle == HALF_CIRCLE: return [name, val] else: if (name != "") and (val != ""): string = name + ", " + val else: string = name + val return [string] elif generation == 6: if self.circle == FULL_CIRCLE: return [p_pn.get_first_name(), p_pn.get_surname(), val] elif self.circle == HALF_CIRCLE: return [p_pn.get_first_name(), p_pn.get_surname(), val] else: if (p_pn.get_first_name() != "") and (p_pn.get_surname() != ""): name = p_pn.get_first_name() + " " + p_pn.get_surname() else: name = p_pn.get_first_name() + p_pn.get_surname() return [name, val] else: return [p_pn.get_first_name(), p_pn.get_surname(), val] def get_max_width_for_circles(self, rad1, rad2, max_centering_proportion): r""" (the "r" in the above line is to keep pylint happy) __ /__\ <- compute the line width which is drawable between 2 circles. / _ \ max_centering_proportion : 0, touching the circle1, 1, | |_| | touching the circle2, 0.5 : middle between the 2 circles | | \ / \__/ basically, max_centering_proportion is max_centering_proportion/nb_lines """ # radius at the center of the 2 circles rmid = rad2 - (rad2-rad1)*max_centering_proportion return sin(acos(rmid/rad2)) * rad2 * 2 def get_max_width_for_circles_line(self, rad1, rad2, line, nb_lines, centering=False): r""" (the "r" in the above line is to keep pylint happy) __ /__\ <- compute the line width which is drawable between 2 circles. / _ \ instead of a max_centering_proportion, you get a | |_| | line/nb_lines position. (we suppose that lines have the | | same heights.) for example, if you've 2 lines to draw, \ / line 2 max width is at the 2/3 between the 2 circles \__/ """ if centering: return self.get_max_width_for_circles(rad1, rad2, 1.0) else: return self.get_max_width_for_circles(rad1, rad2, line/float(nb_lines+1)) def get_optimized_font_size_for_text(self, rad1, rad2, text, font, centering=False): """ a text can be several lines find the font size equals or lower than font.get_size() which fit between rad1 and rad2 to display the text. centering is a special case when you've the full circle available to draw the text in the middle of it """ min_font_size = font.get_size() i = 1 nb_lines = len(text) for line in text: font_size = self.get_optimized_font_size( line, font, self.get_max_width_for_circles_line(rad1, rad2, i, nb_lines, centering)) i += 1 if min_font_size > font_size: min_font_size = font_size return min_font_size def get_optimized_font_size(self, line, font, max_width): """ for a given width, guess the best font size which is equals or smaller than font which make line fit into max_width """ test_font = FontStyle(font) width = utils.pt2cm(self.doc.string_width(test_font, line)) while width > max_width and test_font.get_size() > 1: test_font.set_size(test_font.get_size() -1) width = utils.pt2cm(self.doc.string_width(test_font, line)) return test_font.get_size() def get_optimized_style_sheet(self, title, max_circular, block_size, map_style_from_single, map_paragraphs_colors_to_graphics, make_background_white): """ returns an optimized (modified) style sheet which make fanchart look nicer """ new_style_sheet = self.doc.get_style_sheet() if not new_style_sheet: return self.doc.get_style_sheet() # update title font size pstyle_name = 'FC-Title' p_style = new_style_sheet.get_paragraph_style(pstyle_name) if p_style: title_font = p_style.get_font() if title_font: title_width = utils.pt2cm( self.doc.string_multiline_width(title_font, title)) while (title_width > self.doc.get_usable_width() and title_font.get_size() > 1): title_font.set_size(title_font.get_size()-1) title_width = utils.pt2cm( self.doc.string_multiline_width(title_font, title)) new_style_sheet.add_paragraph_style(pstyle_name, p_style) # biggest font allowed is the one of the fist generation, after, # always lower than the previous one p_style = new_style_sheet.get_paragraph_style(self.text_style[0]) font = None if p_style: font = p_style.get_font() if font: previous_generation_font_size = font.get_size() for generation in range(0, self.max_generations): gstyle_name = self.graphic_style[generation] pstyle_name = self.text_style[generation] g_style = new_style_sheet.get_draw_style(gstyle_name) # p_style is a copy of 'FC-Text' - use different style # to be able to auto change some fonts for some generations if map_style_from_single: p_style = new_style_sheet.get_paragraph_style('FC-Text') else: p_style = new_style_sheet.get_paragraph_style(pstyle_name) if g_style and p_style: # set graphic colors to paragraph colors, # while it's functionnaly # the same for fanchart or make backgrounds white if make_background_white: g_style.set_fill_color((255, 255, 255)) new_style_sheet.add_draw_style(gstyle_name, g_style) elif map_paragraphs_colors_to_graphics: pstyle = new_style_sheet.get_paragraph_style( pstyle_name) if pstyle: g_style.set_fill_color( pstyle.get_background_color()) new_style_sheet.add_draw_style(gstyle_name, g_style) # adapt font size if too big segments = 2**generation if generation < min(max_circular, self.max_generations): # adpatation for circular fonts rad1, rad2 = self.get_circular_radius( block_size, generation, self.circle) font = p_style.get_font() if font: min_font_size = font.get_size() # find the smallest font required for index in range(segments - 1, 2*segments - 1): if self.map[index]: font_size = \ self.get_optimized_font_size_for_text( rad1, rad2, self.text[index], p_style.get_font(), (self.circle == FULL_CIRCLE and generation == 0) ) if font_size < min_font_size: min_font_size = font_size font.set_size(min(previous_generation_font_size, min_font_size)) else: # adaptation for radial fonts # find the largest string for the generation longest_line = "" longest_width = 0 for index in range(segments - 1, 2*segments - 1): if self.map[index]: for line in self.text[index]: width = utils.pt2cm( self.doc.string_multiline_width( p_style.get_font(), line)) if width > longest_width: longest_line = line longest_width = width # determine maximum width allowed for this generation rad1, rad2 = self.get_radial_radius( block_size, generation, self.circle) max_width = rad2 - rad1 # reduce the font so that longest_width # fit into max_width font = p_style.get_font() if font: font.set_size(min(previous_generation_font_size, self.get_optimized_font_size( longest_line, p_style.get_font(), max_width))) # redefine the style new_style_sheet.add_paragraph_style(pstyle_name, p_style) font = p_style.get_font() if font: previous_generation_font_size = font.get_size() # finished return new_style_sheet def draw_circular(self, _x_, _y_, start_angle, max_angle, size, generation): segments = 2**generation delta = max_angle / segments end_angle = start_angle text_angle = start_angle - 270 + (delta / 2.0) rad1, rad2 = self.get_circular_radius(size, generation, self.circle) graphic_style = self.graphic_style[generation] for index in range(segments - 1, 2*segments - 1): start_angle = end_angle end_angle = start_angle + delta (_xc, _yc) = draw_wedge(self.doc, graphic_style, _x_, _y_, rad2, start_angle, end_angle, self.map[index] or self.draw_empty, rad1) if self.map[index]: if (generation == 0) and self.circle == FULL_CIRCLE: _yc = _y_ person = self.database.get_person_from_handle(self.map[index]) mark = utils.get_person_mark(self.database, person) self.doc.rotate_text(graphic_style, self.text[index], _xc, _yc, text_angle, mark) text_angle += delta def get_radial_radius(self, size, generation, circle): """ determine the radius """ if circle == FULL_CIRCLE: rad1 = size * ((generation * 2) - 5) rad2 = size * ((generation * 2) - 3) elif circle == HALF_CIRCLE: rad1 = size * ((generation * 2) - 3) rad2 = size * ((generation * 2) - 1) else: # quarter circle rad1 = size * ((generation * 2) - 2) rad2 = size * (generation * 2) return rad1, rad2 def get_circular_radius(self, size, generation, circle): """ determine the radius """ return size * generation, size * (generation + 1) def draw_radial(self, _x_, _y_, start_angle, max_angle, size, generation): segments = 2**generation delta = max_angle / segments end_angle = start_angle text_angle = start_angle - delta / 2.0 graphic_style = self.graphic_style[generation] rad1, rad2 = self.get_radial_radius(size, generation, self.circle) for index in range(segments - 1, 2*segments - 1): start_angle = end_angle end_angle = start_angle + delta (_xc, _yc) = draw_wedge(self.doc, graphic_style, _x_, _y_, rad2, start_angle, end_angle, self.map[index] or self.draw_empty, rad1) text_angle += delta if self.map[index]: person = self.database.get_person_from_handle(self.map[index]) mark = utils.get_person_mark(self.database, person) if (self.radial == RADIAL_UPRIGHT and (start_angle >= 90) and (start_angle < 270)): self.doc.rotate_text(graphic_style, self.text[index], _xc, _yc, text_angle + 180, mark) else: self.doc.rotate_text(graphic_style, self.text[index], _xc, _yc, text_angle, mark)
class TagReport(Report): """ Tag Report """ def __init__(self, database, options, user): """ Create the TagReport object that produces the report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. tag - The tag each object must match to be included. name_format - Preferred format to display names of people incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) menu = options.menu self.set_locale(menu.get_option_by_name('trans').get_value()) stdoptions.run_date_format_option(self, menu) stdoptions.run_private_data_option(self, menu) living_opt = stdoptions.run_living_people_option(self, menu, self._locale) self.database = CacheProxyDb(self.database) self._lv = menu.get_option_by_name('living_people').get_value() for (value, description) in living_opt.get_items(xml_items=True): if value == self._lv: living_desc = self._(description) break self.living_desc = self._("(Living people: %(option_name)s)" ) % {'option_name' : living_desc} self.tag = menu.get_option_by_name('tag').get_value() if not self.tag: raise ReportError( _('Tag Report'), _('You must first create a tag before running this report.')) stdoptions.run_name_format_option(self, menu) def write_report(self): self.doc.start_paragraph("TR-Title") # feature request 2356: avoid genitive form title = self._("Tag Report for %s Items") % self.tag mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() if self._lv != LivingProxyDb.MODE_INCLUDE_ALL: self.doc.start_paragraph("TR-ReportSubtitle") self.doc.write_text(self.living_desc) self.doc.end_paragraph() self.write_people() self.write_families() self.write_events() self.write_places() self.write_notes() self.write_media() self.write_repositories() self.write_sources() self.write_citations() def write_people(self): """ write the people associated with the tag """ plist = self.database.iter_person_handles() filter_class = GenericFilterFactory('Person') a_filter = filter_class() a_filter.add_rule(rules.person.HasTag([self.tag])) ind_list = a_filter.apply(self.database, plist) if not ind_list: return self.doc.start_paragraph("TR-Heading") header = self._("People") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('PeopleTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Name")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Birth")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Death")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for person_handle in ind_list: person = self.database.get_person_from_handle(person_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(person.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() name = self._name_display.display(person) mark = utils.get_person_mark(self.database, person) self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(name, mark) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') birth_ref = person.get_birth_ref() if birth_ref: event = self.database.get_event_from_handle(birth_ref.ref) self.doc.write_text(self._get_date(event.get_date_object())) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') death_ref = person.get_death_ref() if death_ref: event = self.database.get_event_from_handle(death_ref.ref) self.doc.write_text(self._get_date(event.get_date_object())) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_families(self): """ write the families associated with the tag """ flist = self.database.iter_family_handles() filter_class = GenericFilterFactory('Family') a_filter = filter_class() a_filter.add_rule(rules.family.HasTag([self.tag])) fam_list = a_filter.apply(self.database, flist) if not fam_list: return self.doc.start_paragraph("TR-Heading") header = self._("Families") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('FamilyTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Father")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Mother")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Relationship")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for family_handle in fam_list: family = self.database.get_family_from_handle(family_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(family.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') father_handle = family.get_father_handle() if father_handle: father = self.database.get_person_from_handle(father_handle) mark = utils.get_person_mark(self.database, father) self.doc.write_text(self._name_display.display(father), mark) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') mother_handle = family.get_mother_handle() if mother_handle: mother = self.database.get_person_from_handle(mother_handle) mark = utils.get_person_mark(self.database, mother) self.doc.write_text(self._name_display.display(mother), mark) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') relation = family.get_relationship() self.doc.write_text(str(relation)) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_events(self): """ write the events associated with the tag """ elist = self.database.get_event_handles() filter_class = GenericFilterFactory('Event') a_filter = filter_class() a_filter.add_rule(rules.event.HasTag([self.tag])) event_list = a_filter.apply(self.database, elist) if not event_list: return self.doc.start_paragraph("TR-Heading") header = self._("Events") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('EventTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Type")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Participants")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Date")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for event_handle in event_list: event = self.database.get_event_from_handle(event_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(event.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(self._(self._get_type(event.get_type()))) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(get_participant_from_event(self.database, event_handle)) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') date = self._get_date(event.get_date_object()) if date: self.doc.write_text(date) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_places(self): """ write the places associated with the tag """ plist = self.database.get_place_handles() filter_class = GenericFilterFactory('Place') a_filter = filter_class() a_filter.add_rule(rules.place.HasTag([self.tag])) place_list = a_filter.apply(self.database, plist) if not place_list: return self.doc.start_paragraph("TR-Heading") header = self._("Places") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('PlaceTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Title")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Name")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Type")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for place_handle in place_list: place = self.database.get_place_from_handle(place_handle) place_title = _pd.display(self.database, place) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(place.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(place_title) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(place.get_name().get_value()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(str(place.get_type())) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_notes(self): """ write the notes associated with the tag """ nlist = self.database.get_note_handles() filter_class = GenericFilterFactory('Note') a_filter = filter_class() a_filter.add_rule(rules.note.HasTag([self.tag])) note_list = a_filter.apply(self.database, nlist) if not note_list: return self.doc.start_paragraph("TR-Heading") header = self._("Notes") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('NoteTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Type")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell', 2) self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Text")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for note_handle in note_list: note = self.database.get_note_from_handle(note_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(note.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') note_type = note.get_type() self.doc.write_text(str(note_type)) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell', 2) self.doc.write_styled_note( note.get_styledtext(), note.get_format(), 'TR-Note', contains_html=((note.get_type() == NoteType.HTML_CODE))) self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_media(self): """ write the media associated with the tag """ mlist = self.database.get_media_handles(sort_handles=True, locale=self._locale) filter_class = GenericFilterFactory('Media') a_filter = filter_class() a_filter.add_rule(rules.media.HasTag([self.tag])) media_list = a_filter.apply(self.database, mlist) if not media_list: return self.doc.start_paragraph("TR-Heading") header = self._("Media") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('MediaTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Title")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Type")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Date")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for media_handle in media_list: media = self.database.get_media_from_handle(media_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(media.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') title = media.get_description() self.doc.write_text(str(title)) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') mime_type = media.get_mime_type() self.doc.write_text(str(mime_type)) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') date = self._get_date(media.get_date_object()) if date: self.doc.write_text(date) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_repositories(self): """ write the repositories associated with the tag """ rlist = self.database.get_repository_handles() filter_class = GenericFilterFactory('Repository') a_filter = filter_class() a_filter.add_rule(rules.repository.HasTag([self.tag])) repo_list = a_filter.apply(self.database, rlist) if not repo_list: return self.doc.start_paragraph("TR-Heading") header = self._("Repositories") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('ReopTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Name")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Type")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Email Address")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for repo_handle in repo_list: repo = self.database.get_repository_from_handle(repo_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(repo.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(repo.get_name()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(str(repo.get_type())) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') home_page = '' for url in repo.get_url_list(): if url.get_type() == UrlType.EMAIL: home_page = url.get_path() break self.doc.write_text(home_page) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_sources(self): """ write the sources associated with the tag """ slist = self.database.get_source_handles(sort_handles=True, locale=self._locale) filter_class = GenericFilterFactory('Source') a_filter = filter_class() a_filter.add_rule(rules.source.HasTag([self.tag])) source_list = a_filter.apply(self.database, slist) if not source_list: return self.doc.start_paragraph("TR-Heading") header = self._("Source") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('SourceTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Title")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Author")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Publication Information")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for source_handle in source_list: source = self.database.get_source_from_handle(source_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(source.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(source.get_title()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(source.get_author()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(source.get_publication_info()) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_citations(self): """ write the citations associated with the tag """ clist = self.database.get_citation_handles(sort_handles=True, locale=self._locale) filter_class = GenericFilterFactory('Citation') a_filter = filter_class() a_filter.add_rule(rules.citation.HasTag([self.tag])) citation_list = a_filter.apply(self.database, clist) if not citation_list: return self.doc.start_paragraph("TR-Heading") header = self._("Citations") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('CitationTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Volume/Page")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Date")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Source")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for citation_handle in citation_list: citation = self.database.get_citation_from_handle(citation_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(citation.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(citation.get_page()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') date = self._get_date(citation.get_date_object()) if date: self.doc.write_text(date) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') source_handle = citation.get_reference_handle() source = self.database.get_source_from_handle(source_handle) self.doc.write_text(source.get_title()) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table()
class RecordsReport(Report): """ Records Report """ def __init__(self, database, options, user): """ This report needs the following parameters (class variables) that come in the options class. incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) menu = options.menu self.set_locale(options.menu.get_option_by_name('trans').get_value()) stdoptions.run_private_data_option(self, menu) living_opt = stdoptions.run_living_people_option( self, menu, self._locale) self.database = CacheProxyDb(self.database) self._lv = menu.get_option_by_name('living_people').get_value() for (value, description) in living_opt.get_items(xml_items=True): if value == self._lv: living_desc = self._(description) break self.living_desc = self._("(Living people: %(option_name)s)") % { 'option_name': living_desc } filter_option = menu.get_option_by_name('filter') self.filter = filter_option.get_filter() self.top_size = menu.get_option_by_name('top_size').get_value() self.callname = menu.get_option_by_name('callname').get_value() self.footer = menu.get_option_by_name('footer').get_value() self.include = {} for (text, varname, default) in RECORDS: self.include[varname] = menu.get_option_by_name( varname).get_value() self._nf = stdoptions.run_name_format_option(self, menu) def write_report(self): """ Build the actual report. """ records = find_records(self.database, self.filter, self.top_size, self.callname, trans_text=self._, name_format=self._nf, living_mode=self._lv, user=self._user) self.doc.start_paragraph('REC-Title') title = self._("Records") mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() self.doc.start_paragraph('REC-Subtitle') filter_name = self.filter.get_name(self._locale) self.doc.write_text("(%s)" % filter_name) self.doc.end_paragraph() if self._lv != LivingProxyDb.MODE_INCLUDE_ALL: self.doc.start_paragraph('REC-Subtitle') self.doc.write_text(self.living_desc) self.doc.end_paragraph() for (text, varname, top) in records: if not self.include[varname]: continue self.doc.start_paragraph('REC-Heading') self.doc.write_text(self._(text)) self.doc.end_paragraph() last_value = None rank = 0 for (number, (sort, value, name, handletype, handle)) in enumerate(top): mark = None if handletype == 'Person': person = self.database.get_person_from_handle(handle) mark = utils.get_person_mark(self.database, person) elif handletype == 'Family': family = self.database.get_family_from_handle(handle) # librecords.py checks that the family has both # a father and a mother and also that each one is # in the filter if any filter was used, so we don't # have to do any similar checking here, it's been done f_handle = family.get_father_handle() dad = self.database.get_person_from_handle(f_handle) f_mark = utils.get_person_mark(self.database, dad) m_handle = family.get_mother_handle() mom = self.database.get_person_from_handle(m_handle) m_mark = utils.get_person_mark(self.database, mom) else: raise ReportError( _("Option '%(opt_name)s' is present " "in %(file)s\n but is not known to " "the module. Ignoring...") % { 'opt_name': handletype, 'file': 'libnarrate.py' }) # since the error is very unlikely I reused the string if value != last_value: last_value = value rank = number self.doc.start_paragraph('REC-Normal') self.doc.write_text( self._("%(number)s. ") % {'number': rank + 1}) self.doc.write_markup(str(name), name.get_tags(), mark) if handletype == 'Family': self.doc.write_text('', f_mark) self.doc.write_text('', m_mark) if isinstance(value, Span): tvalue = value.get_repr(dlocale=self._locale) else: tvalue = value self.doc.write_text(" (%s)" % tvalue) self.doc.end_paragraph() self.doc.start_paragraph('REC-Footer') self.doc.write_text(self.footer) self.doc.end_paragraph()
class EndOfLineReport(Report): """ EndOfLine Report """ def __init__(self, database, options, user): """ Create the EndOfLineReport object that produces the report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. name_format - Preferred format to display names incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) menu = options.menu self.set_locale(menu.get_option_by_name('trans').get_value()) stdoptions.run_date_format_option(self, menu) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, self._locale) self.database = CacheProxyDb(self.database) pid = menu.get_option_by_name('pid').get_value() self.center_person = self.database.get_person_from_gramps_id(pid) if self.center_person is None: raise ReportError(_("Person %s is not in the Database") % pid) stdoptions.run_name_format_option(self, menu) # eol_map is a map whose: # keys are the generations of the people # values are a map whose: # keys are person handles # values are an array whose: # elements are an array of ancestor person handles that link # the eol person handle to the person or interest # eol_map[generation][person_handle][pedigree_idx][ancestor_handle_idx] # # There is an array of pedigrees because one person could show up twice # in one generation (descendants marrying). Most people only have one # pedigree. # # eol_map is populated by get_eol() which calls itself recursively. self.eol_map = {} self.get_eol(self.center_person, 1, []) def get_eol(self, person, gen, pedigree): """ Recursively find the end of the line for each person """ person_handle = person.get_handle() new_pedigree = list(pedigree) + [person_handle] person_is_eol = False families = person.get_parent_family_handle_list() if person_handle in pedigree: # This is a severe error! # It indicates a loop in ancestry: A -> B -> A person_is_eol = True elif not families: person_is_eol = True else: for family_handle in families: family = self.database.get_family_from_handle(family_handle) father_handle = family.get_father_handle() mother_handle = family.get_mother_handle() if father_handle: father = self.database.get_person_from_handle(father_handle) self.get_eol(father, gen+1, new_pedigree) if mother_handle: mother = self.database.get_person_from_handle(mother_handle) self.get_eol(mother, gen+1, new_pedigree) if not father_handle or not mother_handle: person_is_eol = True if person_is_eol: # This person is the end of a line if gen not in self.eol_map: self.eol_map[gen] = {} if person_handle not in self.eol_map[gen]: self.eol_map[gen][person_handle] = [] self.eol_map[gen][person_handle].append(new_pedigree) def write_report(self): """ The routine that actually creates the report. At this point, the document is opened and ready for writing. """ pname = self._name_display.display(self.center_person) self.doc.start_paragraph("EOL-Title") # feature request 2356: avoid genitive form title = self._("End of Line Report for %s") % pname mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() self.doc.start_paragraph("EOL-Subtitle") # feature request 2356: avoid genitive form title = self._("All the ancestors of %s who are missing a parent" ) % pname self.doc.write_text(title) self.doc.end_paragraph() self.doc.start_table('EolTable', 'EOL-Table') for generation, handles in sorted(self.eol_map.items()): self.write_generation_row(generation) for person_handle, pedigrees in handles.items(): self.write_person_row(person_handle) list(map(self.write_pedigree_row, pedigrees)) self.doc.end_table() def write_generation_row(self, generation): """ Write out a row in the table showing the generation. """ self.doc.start_row() self.doc.start_cell('EOL_GenerationCell', 2) self.doc.start_paragraph('EOL-Generation') self.doc.write_text(self._("Generation %d") % generation) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() def write_person_row(self, person_handle): """ Write a row in the table with information about the given person. """ person = self.database.get_person_from_handle(person_handle) name = self._name_display.display(person) mark = utils.get_person_mark(self.database, person) birth_date = "" birth_ref = person.get_birth_ref() if birth_ref: event = self.database.get_event_from_handle(birth_ref.ref) birth_date = self._get_date(event.get_date_object()) death_date = "" death_ref = person.get_death_ref() if death_ref: event = self.database.get_event_from_handle(death_ref.ref) death_date = self._get_date(event.get_date_object()) dates = '' if birth_date or death_date: dates = " (%(birth_date)s - %(death_date)s)" % { 'birth_date' : birth_date, 'death_date' : death_date} self.doc.start_row() self.doc.start_cell('EOL-TableCell', 2) self.doc.start_paragraph('EOL-Normal') self.doc.write_text(name, mark) self.doc.write_text(dates) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() def write_pedigree_row(self, pedigree): """ Write a row in the table with with the person's family line. pedigree is an array containing the names of the people in the pedigree """ names = [] for person_handle in pedigree: person = self.database.get_person_from_handle(person_handle) names.append(self._name_display.display(person)) text = " -- ".join(names) self.doc.start_row() self.doc.start_cell('EOL-TableCell') self.doc.end_cell() self.doc.start_cell('EOL-TableCell') self.doc.start_paragraph('EOL-Pedigree') self.doc.write_text(text) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row()
class RelGraphReport(Report): def __init__(self, database, options, user): """ Create RelGraphReport object that produces the report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. filter - Filter to be applied to the people of the database. The option class carries its number, and the function returning the list of filters. arrow - Arrow styles for heads and tails. showfamily - Whether to show family nodes. inc_id - Whether to include IDs. url - Whether to include URLs. inclimg - Include images or not imgpos - Image position, above/beside name color - Whether to use outline, colored outline or filled color in graph color_males - Colour to apply to males color_females - Colour to apply to females color_unknown - Colour to apply to unknown genders color_families - Colour to apply to families dashed - Whether to use dashed lines for non-birth relationships use_roundedcorners - Whether to use rounded corners for females name_format - Preferred format to display names incl_private - Whether to include private data event_choice - Whether to include dates and/or places occupation - Whether to include occupations living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) menu = options.menu get_option_by_name = options.menu.get_option_by_name get_value = lambda name: get_option_by_name(name).get_value() self.set_locale(menu.get_option_by_name('trans').get_value()) stdoptions.run_date_format_option(self, menu) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, self._locale) self.database = CacheProxyDb(self.database) self._db = self.database self.includeid = get_value('inc_id') self.includeurl = get_value('url') self.includeimg = get_value('includeImages') self.imgpos = get_value('imageOnTheSide') self.use_roundedcorners = get_value('useroundedcorners') self.adoptionsdashed = get_value('dashed') self.show_families = get_value('showfamily') self.use_subgraphs = get_value('usesubgraphs') self.event_choice = get_value('event_choice') self.occupation = get_value('occupation') self.use_html_output = False self.colorize = get_value('color') color_males = get_value('colormales') color_females = get_value('colorfemales') color_unknown = get_value('colorunknown') color_families = get_value('colorfamilies') self.colors = { 'male': color_males, 'female': color_females, 'unknown': color_unknown, 'family': color_families } arrow_str = get_value('arrow') if 'd' in arrow_str: self.arrowheadstyle = 'normal' else: self.arrowheadstyle = 'none' if 'a' in arrow_str: self.arrowtailstyle = 'normal' else: self.arrowtailstyle = 'none' filter_option = get_option_by_name('filter') self._filter = filter_option.get_filter() stdoptions.run_name_format_option(self, menu) pid = get_value('pid') self.center_person = self._db.get_person_from_gramps_id(pid) if self.center_person is None: raise ReportError(_("Person %s is not in the Database") % pid) self.increlname = get_value('increlname') if self.increlname: self.rel_calc = get_relationship_calculator(reinit=True, clocale=self._locale) if __debug__: self.advrelinfo = get_value('advrelinfo') else: self.advrelinfo = False def write_report(self): person_handles = self._filter.apply(self._db, self._db.iter_person_handles(), user=self._user) person_handles = self.sort_persons(person_handles) if len(person_handles) > 1: if self._user: self._user.begin_progress(_("Relationship Graph"), _("Generating report"), len(person_handles) * 2) self.add_persons_and_families(person_handles) self.add_child_links_to_families(person_handles) if self._user: self._user.end_progress() def sort_persons(self, person_handle_list): "sort persons by close relations" # first make a list of all persons who don't have any parents root_nodes = list() for person_handle in person_handle_list: person = self.database.get_person_from_handle(person_handle) has_parent = False for parent_handle in find_parents(self.database, person): if parent_handle not in person_handle_list: continue has_parent = True if not has_parent: root_nodes.append(person_handle) # now start from all root nodes we found and traverse their trees outlist = list() p_done = set() for person_handle in root_nodes: todolist = list() todolist.append(person_handle) while len(todolist) > 0: # take the first person from todolist and do sanity check cur = todolist.pop(0) if cur in p_done: continue if cur not in person_handle_list: p_done.add(cur) continue person = self.database.get_person_from_handle(cur) # first check whether both parents are added missing_parents = False for parent_handle in find_parents(self.database, person): if not parent_handle or parent_handle in p_done: continue if parent_handle not in person_handle_list: continue todolist.insert(0, parent_handle) missing_parents = True # if one of the parents is still missing, wait for them if missing_parents: continue # add person to the sorted output outlist.append(cur) p_done.add(cur) # add all spouses and children to the todo list family_list = person.get_family_handle_list() for fam_handle in family_list: family = self.database.get_family_from_handle(fam_handle) if family is None: continue if (family.get_father_handle() and family.get_father_handle() != cur): todolist.insert(0, family.get_father_handle()) if (family.get_mother_handle() and family.get_mother_handle() != cur): todolist.insert(0, family.get_mother_handle()) for child_ref in family.get_child_ref_list(): todolist.append(child_ref.ref) # finally store the result assert len(person_handle_list) == len(outlist) return outlist def add_child_links_to_families(self, person_handles): """ returns string of Graphviz edges linking parents to families or children """ # Hash people in a dictionary for faster inclusion checking person_dict = dict([handle, 1] for handle in person_handles) for person_handle in person_handles: if self._user: self._user.step_progress() person = self._db.get_person_from_handle(person_handle) p_id = person.get_gramps_id() for fam_handle in person.get_parent_family_handle_list(): family = self._db.get_family_from_handle(fam_handle) father_handle = family.get_father_handle() mother_handle = family.get_mother_handle() for child_ref in family.get_child_ref_list(): if child_ref.ref == person_handle: frel = child_ref.frel mrel = child_ref.mrel break if (self.show_families and ((father_handle and father_handle in person_dict) or (mother_handle and mother_handle in person_dict))): # Link to the family node if either parent is in graph self.add_family_link(p_id, family, frel, mrel) else: # Link to the parents' nodes directly, if they are in graph if father_handle and father_handle in person_dict: self.add_parent_link(p_id, father_handle, frel) if mother_handle and mother_handle in person_dict: self.add_parent_link(p_id, mother_handle, mrel) def add_family_link(self, p_id, family, frel, mrel): "Links the child to a family" style = 'solid' adopted = ((int(frel) != ChildRefType.BIRTH) or (int(mrel) != ChildRefType.BIRTH)) # If birth relation to father is NONE, meaning there is no father and # if birth relation to mother is BIRTH then solid line if (int(frel) == ChildRefType.NONE and int(mrel) == ChildRefType.BIRTH): adopted = False if adopted and self.adoptionsdashed: style = 'dotted' self.doc.add_link(family.get_gramps_id(), p_id, style, self.arrowheadstyle, self.arrowtailstyle) def add_parent_link(self, p_id, parent_handle, rel): "Links the child to a parent" style = 'solid' if (int(rel) != ChildRefType.BIRTH) and self.adoptionsdashed: style = 'dotted' parent = self._db.get_person_from_handle(parent_handle) self.doc.add_link(parent.get_gramps_id(), p_id, style, self.arrowheadstyle, self.arrowtailstyle) def add_persons_and_families(self, person_handles): "adds nodes for persons and their families" # variable to communicate with get_person_label self.use_html_output = False # The list of families for which we have output the node, # so we don't do it twice families_done = set() for person_handle in person_handles: if self._user: self._user.step_progress() # determine per person if we use HTML style label if self.includeimg: self.use_html_output = True person = self._db.get_person_from_handle(person_handle) if person is None: continue p_id = person.get_gramps_id() # Output the person's node label = self.get_person_label(person) (shape, style, color, fill) = self.get_gender_style(person) url = "" if self.includeurl: phan = person_handle dirpath = "ppl/%s/%s" % (phan[-1], phan[-2]) dirpath = dirpath.lower() url = "%s/%s.html" % (dirpath, phan) self.doc.add_node(p_id, label, shape, color, style, fill, url) # Output families where person is a parent if self.show_families: family_list = person.get_family_handle_list() for fam_handle in family_list: family = self._db.get_family_from_handle(fam_handle) if family is None: continue if fam_handle not in families_done: families_done.add(fam_handle) self.__add_family(fam_handle) # If subgraphs are not chosen then each parent is linked # separately to the family. This gives Graphviz greater # control over the layout of the whole graph but # may leave spouses not positioned together. if not self.use_subgraphs: self.doc.add_link(p_id, family.get_gramps_id(), "", self.arrowheadstyle, self.arrowtailstyle) def __add_family(self, fam_handle): """Add a node for a family and optionally link the spouses to it""" fam = self._db.get_family_from_handle(fam_handle) if fam is None: return fam_id = fam.get_gramps_id() m_type = m_date = m_place = "" d_type = d_date = d_place = "" for event_ref in fam.get_event_ref_list(): event = self._db.get_event_from_handle(event_ref.ref) if event is None: continue if (event.type == EventType.MARRIAGE and (event_ref.get_role() == EventRoleType.FAMILY or event_ref.get_role() == EventRoleType.PRIMARY)): m_type = event.type m_date = self.get_date_string(event) if not (self.event_choice == 3 and m_date): m_place = self.get_place_string(event) break if (event.type == EventType.DIVORCE and (event_ref.get_role() == EventRoleType.FAMILY or event_ref.get_role() == EventRoleType.PRIMARY)): d_type = event.type d_date = self.get_date_string(event) if not (self.event_choice == 3 and d_date): d_place = self.get_place_string(event) break labellines = list() if self.includeid == 2: # id on separate line labellines.append("(%s)" % fam_id) if self.event_choice == 7: if m_type: line = m_type.get_abbreviation() if m_date: line += ' %s' % m_date if m_date and m_place: labellines.append(line) line = '' if m_place: line += ' %s' % m_place labellines.append(line) if d_type: line = d_type.get_abbreviation() if d_date: line += ' %s' % d_date if d_date and d_place: labellines.append(line) line = '' if d_place: line += ' %s' % d_place labellines.append(line) else: if m_date: labellines.append("(%s)" % m_date) if m_place: labellines.append("(%s)" % m_place) label = "\\n".join(labellines) labellines = list() if self.includeid == 1: # id on same line labellines.append("(%s)" % fam_id) if len(label): labellines.append(label) label = ' '.join(labellines) color = "" fill = "" style = "solid" if self.colorize == 'colored': color = self.colors['family'] elif self.colorize == 'filled': fill = self.colors['family'] style = "filled" self.doc.add_node(fam_id, label, "ellipse", color, style, fill) # If subgraphs are used then we add both spouses here and Graphviz # will attempt to position both spouses closely together. # TODO: A person who is a parent in more than one family may only be # positioned next to one of their spouses. The code currently # does not take into account multiple spouses. if self.use_subgraphs: self.doc.start_subgraph(fam_id) f_handle = fam.get_father_handle() m_handle = fam.get_mother_handle() if f_handle: father = self._db.get_person_from_handle(f_handle) self.doc.add_link(father.get_gramps_id(), fam_id, "", self.arrowheadstyle, self.arrowtailstyle) if m_handle: mother = self._db.get_person_from_handle(m_handle) self.doc.add_link(mother.get_gramps_id(), fam_id, "", self.arrowheadstyle, self.arrowtailstyle) self.doc.end_subgraph() def get_gender_style(self, person): "return gender specific person style" gender = person.get_gender() shape = "box" style = "solid" color = "" fill = "" if gender == person.FEMALE and self.use_roundedcorners: style = "rounded" elif gender == person.UNKNOWN: shape = "hexagon" if person == self.center_person and self.increlname: shape = "octagon" if self.colorize == 'colored': if gender == person.MALE: color = self.colors['male'] elif gender == person.FEMALE: color = self.colors['female'] else: color = self.colors['unknown'] elif self.colorize == 'filled': style += ",filled" if gender == person.MALE: fill = self.colors['male'] elif gender == person.FEMALE: fill = self.colors['female'] else: fill = self.colors['unknown'] return(shape, style, color, fill) def get_person_label(self, person): "return person label string" # see if we have an image to use for this person image_path = None if self.use_html_output: media_list = person.get_media_list() if len(media_list) > 0: media_handle = media_list[0].get_reference_handle() media = self._db.get_media_from_handle(media_handle) media_mime_type = media.get_mime_type() if media_mime_type[0:5] == "image": image_path = get_thumbnail_path( media_path_full(self._db, media.get_path()), rectangle=media_list[0].get_rectangle()) # test if thumbnail actually exists in thumbs # (import of data means media files might not be present image_path = find_file(image_path) label = "" line_delimiter = '\\n' # If we have an image, then start an HTML table; remember to close # the table afterwards! # # This isn't a free-form HTML format here...just a few keywords that # happen to be # similar to keywords commonly seen in HTML. For additional # information on what # is allowed, see: # # http://www.graphviz.org/info/shapes.html#html # if self.use_html_output and image_path: line_delimiter = '<BR/>' label += '<TABLE BORDER="0" CELLSPACING="2" CELLPADDING="0" ' label += 'CELLBORDER="0"><TR><TD></TD><TD>' label += '<IMG SRC="%s"/></TD><TD></TD>' % image_path if self.imgpos == 0: #trick it into not stretching the image label += '</TR><TR><TD COLSPAN="3">' else: label += '<TD>' else: #no need for html label with this person self.use_html_output = False # at the very least, the label must have the person's name p_name = self._name_display.display(person) if self.use_html_output: # avoid < and > in the name, as this is html text label += p_name.replace('<', '<').replace('>', '>') else: label += p_name p_id = person.get_gramps_id() if self.includeid == 1: # same line label += " (%s)" % p_id elif self.includeid == 2: # own line label += "%s(%s)" % (line_delimiter, p_id) if self.event_choice != 0: b_date, d_date, b_place, d_place, b_type, d_type = \ self.get_event_strings(person) if self.event_choice in [1, 2, 3, 4, 5] and (b_date or d_date): label += '%s(' % line_delimiter if b_date: label += '%s' % b_date label += ' - ' if d_date: label += '%s' % d_date label += ')' if (self.event_choice in [2, 3, 5, 6] and (b_place or d_place) and not (self.event_choice == 3 and (b_date or d_date)) ): label += '%s(' % line_delimiter if b_place: label += '%s' % b_place label += ' - ' if d_place: label += '%s' % d_place label += ')' if self.event_choice == 7: if b_type: label += '%s%s' % (line_delimiter, b_type.get_abbreviation()) if b_date: label += ' %s' % b_date if b_place: label += ' %s' % b_place if d_type: label += '%s%s' % (line_delimiter, d_type.get_abbreviation()) if d_date: label += ' %s' % d_date if d_place: label += ' %s' % d_place if self.increlname and self.center_person != person: # display relationship info if self.advrelinfo: (relationship, _ga, _gb) = self.rel_calc.get_one_relationship( self._db, self.center_person, person, extra_info=True, olocale=self._locale) if relationship: label += "%s(%s Ga=%d Gb=%d)" % (line_delimiter, relationship, _ga, _gb) else: relationship = self.rel_calc.get_one_relationship( self._db, self.center_person, person, olocale=self._locale) if relationship: label += "%s(%s)" % (line_delimiter, relationship) if self.occupation > 0: event_refs = person.get_primary_event_ref_list() events = [event for event in [self._db.get_event_from_handle(ref.ref) for ref in event_refs] if event.get_type() == EventType(EventType.OCCUPATION)] if len(events) > 0: events.sort(key=lambda x: x.get_date_object()) if self.occupation == 1: occupation = events[-1].get_description() if occupation: label += "%s(%s)" % (line_delimiter, occupation) elif self.occupation == 2: for evt in events: date = self.get_date_string(evt) place = self.get_place_string(evt) desc = evt.get_description() if not date and not desc and not place: continue label += '%s(' % line_delimiter if date: label += '%s' % date if desc: label += ' ' if desc: label += '%s' % desc if place: if date or desc: label += self._(', ') # Arabic OK label += '%s' % place label += ')' # see if we have a table that needs to be terminated if self.use_html_output: label += '</TD></TR></TABLE>' return label else: # non html label is enclosed by "" so escape other " return label.replace('"', '\\\"') def get_event_strings(self, person): "returns tuple of birth/christening and death/burying date strings" birth_date = birth_place = death_date = death_place = "" birth_type = death_type = "" birth_event = get_birth_or_fallback(self._db, person) if birth_event: birth_type = birth_event.type birth_date = self.get_date_string(birth_event) birth_place = self.get_place_string(birth_event) death_event = get_death_or_fallback(self._db, person) if death_event: death_type = death_event.type death_date = self.get_date_string(death_event) death_place = self.get_place_string(death_event) return (birth_date, death_date, birth_place, death_place, birth_type, death_type) def get_date_string(self, event): """ return date string for an event label. Based on the data availability and preferences, we select one of the following for a given event: year only complete date empty string """ if event and event.get_date_object() is not None: event_date = event.get_date_object() if event_date.get_year_valid(): if self.event_choice in [4, 5]: return '%i' % event_date.get_year() elif self.event_choice in [1, 2, 3, 7]: return self._get_date(event_date) return '' def get_place_string(self, event): """ return place string for an event label. Based on the data availability and preferences, we select one of the following for a given event: place name empty string """ if event and self.event_choice in [2, 3, 5, 6, 7]: return _pd.display_event(self._db, event) return ''
class DetAncestorReport(Report): """ the Detailed Ancestor Report """ def __init__(self, database, options, user): """ Create the DetAncestorReport object that produces the report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. gen - Maximum number of generations to include. inc_id - Whether to include Gramps IDs pagebgg - Whether to include page breaks between generations. pageben - Whether to include page break before End Notes. firstName - Whether to use first names instead of pronouns. fulldate - Whether to use full dates instead of just year. listchildren - Whether to list children. list_children_spouses - Whether to list the spouses of the children includenotes - Whether to include notes. incattrs - Whether to include attributes blankplace - Whether to replace missing Places with ___________. blankDate - Whether to replace missing Dates with ___________. calcageflag - Whether to compute age. dupperson - Whether to omit duplicate ancestors (e.g. when distant cousins marry). verbose - Whether to use complete sentences childref - Whether to add descendant references in child list. addimages - Whether to include images. pid - The Gramps ID of the center person for the report. name_format - Preferred format to display names other_events - Whether to include other events. incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) self.map = {} self._user = user menu = options.menu get_option_by_name = menu.get_option_by_name get_value = lambda name: get_option_by_name(name).get_value() self.set_locale(menu.get_option_by_name('trans').get_value()) stdoptions.run_date_format_option(self, menu) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, self._locale) self.database = CacheProxyDb(self.database) self._db = self.database self.max_generations = get_value('gen') self.pgbrk = get_value('pagebbg') self.pgbrkenotes = get_value('pageben') self.fulldate = get_value('fulldates') use_fulldate = self.fulldate self.listchildren = get_value('listc') self.list_children_spouses = get_value('listc_spouses') self.includenotes = get_value('incnotes') use_call = get_value('usecall') blankplace = get_value('repplace') blankdate = get_value('repdate') self.calcageflag = get_value('computeage') self.dupperson = get_value('omitda') self.verbose = get_value('verbose') self.childref = get_value('desref') self.addimages = get_value('incphotos') self.inc_names = get_value('incnames') self.inc_events = get_value('incevents') self.inc_addr = get_value('incaddresses') self.inc_sources = get_value('incsources') self.inc_srcnotes = get_value('incsrcnotes') self.inc_attrs = get_value('incattrs') self.initial_sosa = get_value('initial_sosa') self.want_ids = get_value('inc_id') pid = get_value('pid') self.other_events = get_value('incotherevents') self.center_person = self._db.get_person_from_gramps_id(pid) if self.center_person is None: raise ReportError(_("Person %s is not in the Database") % pid) stdoptions.run_name_format_option(self, menu) self._nd = self._name_display self.place_format = menu.get_option_by_name("place_format").get_value() self.gen_handles = {} self.prev_gen_handles = {} if blankdate: empty_date = EMPTY_ENTRY else: empty_date = "" if blankplace: empty_place = EMPTY_ENTRY else: empty_place = "" self.__narrator = Narrator(self._db, self.verbose, use_call, use_fulldate, empty_date, empty_place, nlocale=self._locale, place_format=self.place_format, get_endnote_numbers=self.endnotes) self.bibli = Bibliography(Bibliography.MODE_DATE | Bibliography.MODE_PAGE) def apply_filter(self, person_handle, index): """ recurse up through the generations """ if (not person_handle) or (index >= 2**self.max_generations): return self.map[index] = person_handle person = self._db.get_person_from_handle(person_handle) family_handle = person.get_main_parents_family_handle() if family_handle: family = self._db.get_family_from_handle(family_handle) self.apply_filter(family.get_father_handle(), index * 2) self.apply_filter(family.get_mother_handle(), (index * 2) + 1) def write_report(self): self.apply_filter(self.center_person.get_handle(), 1) name = self._nd.display_name(self.center_person.get_primary_name()) if not name: name = self._("Unknown") self.doc.start_paragraph("DAR-Title") # feature request 2356: avoid genitive form title = self._("Ancestral Report for %s") % name mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() generation = 0 for key in sorted(self.map): if generation == 0 or key >= 2**generation: if self.pgbrk and generation > 0: self.doc.page_break() self.doc.start_paragraph("DAR-Generation") text = self._("Generation %d") % (generation + 1) mark = IndexMark(text, INDEX_TYPE_TOC, 2) self.doc.write_text(text, mark) self.doc.end_paragraph() generation += 1 if self.childref: self.prev_gen_handles = self.gen_handles.copy() self.gen_handles.clear() person_handle = self.map[key] person = self._db.get_person_from_handle(person_handle) self.gen_handles[person_handle] = key dupperson = self.write_person(key) if dupperson == 0: # Is this a duplicate ind record if self.listchildren or self.inc_events: for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) mother_handle = family.get_mother_handle() if (mother_handle is None or mother_handle not in iter(self.map.values()) or person.get_gender() == Person.FEMALE): # The second test above also covers the 1. person's # mate, which is not an ancestor and as such is not # included in the self.map dictionary if self.listchildren: self.write_children(family) if self.inc_events: self.write_family_events(family) if self.inc_sources: if self.pgbrkenotes: self.doc.page_break() # it ignores language set for Note type (use locale) endnotes.write_endnotes(self.bibli, self._db, self.doc, printnotes=self.inc_srcnotes, elocale=self._locale) def _get_s_s(self, key): """returns Sosa-Stradonitz (a.k.a. Kekule or Ahnentafel) number""" generation = int(math.floor(math.log(key, 2))) # 0 gen_start = pow(2, generation) # 1 new_gen_start = self.initial_sosa * gen_start # 3 return new_gen_start + (key - gen_start) # 3+0 def write_person(self, key): """ Output birth, death, parentage, marriage and notes information """ def write_more_header(first, name): """ convenience function """ if first: self.doc.start_paragraph('DAR-MoreHeader') self.doc.write_text( self._('More about %(person_name)s:') % {'person_name': name}) self.doc.end_paragraph() return False person_handle = self.map[key] person = self._db.get_person_from_handle(person_handle) plist = person.get_media_list() self.__narrator.set_subject(person) if self.addimages and len(plist) > 0: photo = plist[0] utils.insert_image(self._db, self.doc, photo, self._user) self.doc.start_paragraph("DAR-First-Entry", "%d." % self._get_s_s(key)) name = self._nd.display(person) if not name: name = self._("Unknown") mark = utils.get_person_mark(self._db, person) self.doc.start_bold() self.doc.write_text(name, mark) if name[-1:] == '.': self.doc.write_text_citation("%s " % self.endnotes(person)) elif name: self.doc.write_text_citation("%s. " % self.endnotes(person)) self.doc.end_bold() if self.want_ids: self.doc.write_text('(%s) ' % person.get_gramps_id()) if self.dupperson: # Check for duplicate record (result of distant cousins marrying) for dkey in sorted(self.map): if dkey >= key: break if self.map[key] == self.map[dkey]: self.doc.write_text( self._("%(name)s is the same person as [%(id_str)s].") % { 'name': '', 'id_str': str(dkey) }) self.doc.end_paragraph() return 1 # Duplicate person if not self.verbose: self.write_parents(person) text = self.__narrator.get_born_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_baptised_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_christened_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_died_string(self.calcageflag) if text: self.doc.write_text_citation(text) text = self.__narrator.get_buried_string() if text: self.doc.write_text_citation(text) if self.verbose: self.write_parents(person) if not key % 2 or key == 1: self.write_marriage(person) self.doc.end_paragraph() if key == 1: self.write_mate(person) notelist = person.get_note_list() if len(notelist) > 0 and self.includenotes: self.doc.start_paragraph("DAR-NoteHeader") # feature request 2356: avoid genitive form self.doc.write_text(self._("Notes for %s") % name) self.doc.end_paragraph() for notehandle in notelist: note = self._db.get_note_from_handle(notehandle) self.doc.write_styled_note( note.get_styledtext(), note.get_format(), "DAR-Entry", contains_html=(note.get_type() == NoteType.HTML_CODE)) first = True if self.inc_names: for alt_name in person.get_alternate_names(): first = write_more_header(first, name) self.doc.start_paragraph('DAR-MoreDetails') atype = self._get_type(alt_name.get_type()) self.doc.write_text_citation( self._('%(type)s: %(value)s%(endnotes)s') % { 'type': self._(atype), 'value': alt_name.get_regular_name(), 'endnotes': self.endnotes(alt_name) }) self.doc.end_paragraph() if self.inc_events: birth_ref = person.get_birth_ref() death_ref = person.get_death_ref() for event_ref in person.get_primary_event_ref_list(): if event_ref == birth_ref or event_ref == death_ref: continue first = write_more_header(first, name) self.write_event(event_ref) if self.other_events: for event_ref in person.get_event_ref_list(): role = event_ref.get_role() if role in (EventRoleType.PRIMARY, EventRoleType.FAMILY): continue first = write_more_header(first, name) self.write_event(event_ref) if self.inc_addr: for addr in person.get_address_list(): first = write_more_header(first, name) self.doc.start_paragraph('DAR-MoreDetails') text = utils.get_address_str(addr) self.doc.write_text(self._('Address: ')) if self.fulldate: date = self._get_date(addr.get_date_object()) else: date = addr.get_date_object().get_year() if date: # translators: needed for Arabic, ignore otherwise self.doc.write_text(self._('%s, ') % date) self.doc.write_text(text) self.doc.write_text_citation(self.endnotes(addr)) self.doc.end_paragraph() if self.inc_attrs: attrs = person.get_attribute_list() if attrs: first = write_more_header(first, name) for attr in attrs: self.doc.start_paragraph('DAR-MoreDetails') attr_name = attr.get_type().type2base() # translators: needed for French, ignore otherwise text = self._("%(type)s: %(value)s%(endnotes)s") % { 'type': self._(attr_name), 'value': attr.get_value(), 'endnotes': self.endnotes(attr) } self.doc.write_text_citation(text) self.doc.end_paragraph() return 0 # Not duplicate person def write_event(self, event_ref): """ write out the event """ text = "" event = self._db.get_event_from_handle(event_ref.ref) if self.fulldate: date = self._get_date(event.get_date_object()) else: date = event.get_date_object().get_year() place = _pd.display_event(self._db, event, self.place_format) self.doc.start_paragraph('DAR-MoreDetails') if date and place: # translators: needed for Arabic, ignore otherwise text += self._('%(str1)s, %(str2)s') % { 'str1': date, 'str2': place } elif date: text += '%s' % date elif place: text += '%s' % self._(place) if event.get_description(): if text: text += ". " text += event.get_description() text += self.endnotes(event) if text: text += ". " event_name = self._(self._get_type(event.get_type())) role = event_ref.get_role() if role in (EventRoleType.PRIMARY, EventRoleType.FAMILY): # translators: needed for French, ignore otherwise text = self._('%(str1)s: %(str2)s') % { 'str1': event_name, 'str2': text } else: primaries = get_participant_from_event(self._db, event_ref.ref) text = self._('%(event_role)s at %(event_name)s ' 'of %(primary_person)s: %(event_text)s') % { 'event_role': self._(role.xml_str()), 'event_name': event_name, 'primary_person': primaries, 'event_text': text } self.doc.write_text_citation(text) if self.inc_attrs: text = "" attr_list = event.get_attribute_list() attr_list.extend(event_ref.get_attribute_list()) for attr in attr_list: if text: # translators: needed for Arabic, ignore otherwise text += self._("; ") attr_name = attr.get_type().type2base() # translators: needed for French, ignore otherwise text += self._("%(type)s: %(value)s%(endnotes)s") % { 'type': self._(attr_name), 'value': attr.get_value(), 'endnotes': self.endnotes(attr) } text = " " + text self.doc.write_text_citation(text) self.doc.end_paragraph() if self.includenotes: # if the event or event reference has a note attached to it, # get the text and format it correctly notelist = event.get_note_list() notelist.extend(event_ref.get_note_list()) for notehandle in notelist: note = self._db.get_note_from_handle(notehandle) self.doc.write_styled_note( note.get_styledtext(), note.get_format(), "DAR-MoreDetails", contains_html=(note.get_type() == NoteType.HTML_CODE)) def write_parents(self, person): """ write the parents """ family_handle = person.get_main_parents_family_handle() if family_handle: family = self._db.get_family_from_handle(family_handle) mother_handle = family.get_mother_handle() father_handle = family.get_father_handle() if mother_handle: mother = self._db.get_person_from_handle(mother_handle) mother_name = self._nd.display_name(mother.get_primary_name()) mother_mark = utils.get_person_mark(self._db, mother) else: mother_name = "" mother_mark = "" if father_handle: father = self._db.get_person_from_handle(father_handle) father_name = self._nd.display_name(father.get_primary_name()) father_mark = utils.get_person_mark(self._db, father) else: father_name = "" father_mark = "" text = self.__narrator.get_child_string(father_name, mother_name) if text: self.doc.write_text(text) if father_mark: self.doc.write_text("", father_mark) if mother_mark: self.doc.write_text("", mother_mark) def write_marriage(self, person): """ Output marriage sentence. """ is_first = True for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) spouse_handle = utils.find_spouse(person, family) if spouse_handle: spouse = self._db.get_person_from_handle(spouse_handle) spouse_mark = utils.get_person_mark(self._db, spouse) else: spouse_mark = None text = self.__narrator.get_married_string(family, is_first, self._nd) if text: self.doc.write_text_citation(text, spouse_mark) if self.want_ids: self.doc.write_text(' (%s)' % family.get_gramps_id()) is_first = False def write_children(self, family): """ List children. :param family: Family :return: """ if not family.get_child_ref_list(): return mother_handle = family.get_mother_handle() if mother_handle: mother = self._db.get_person_from_handle(mother_handle) mother_name = self._nd.display(mother) if not mother_name: mother_name = self._("Unknown") else: mother_name = self._("Unknown") father_handle = family.get_father_handle() if father_handle: father = self._db.get_person_from_handle(father_handle) father_name = self._nd.display(father) if not father_name: father_name = self._("Unknown") else: father_name = self._("Unknown") self.doc.start_paragraph("DAR-ChildTitle") self.doc.write_text( self._("Children of %(mother_name)s and %(father_name)s") % { 'father_name': father_name, 'mother_name': mother_name }) self.doc.end_paragraph() cnt = 1 for child_ref in family.get_child_ref_list(): child_handle = child_ref.ref child = self._db.get_person_from_handle(child_handle) child_name = self._nd.display(child) if not child_name: child_name = self._("Unknown") child_mark = utils.get_person_mark(self._db, child) if self.childref and self.prev_gen_handles.get(child_handle): value = int(self.prev_gen_handles.get(child_handle)) child_name += " [%d]" % self._get_s_s(value) self.doc.start_paragraph("DAR-ChildList", utils.roman(cnt).lower() + ".") cnt += 1 self.__narrator.set_subject(child) if child_name: self.doc.write_text("%s. " % child_name, child_mark) if self.want_ids: self.doc.write_text('(%s) ' % child.get_gramps_id()) self.doc.write_text_citation( self.__narrator.get_born_string() or self.__narrator.get_christened_string() or self.__narrator.get_baptised_string()) self.doc.write_text_citation( self.__narrator.get_died_string() or self.__narrator.get_buried_string()) # if the list_children_spouses option is selected: if self.list_children_spouses: # get the family of the child that contains the spouse # of the child. There may be more than one spouse for each # child family_handle_list = child.get_family_handle_list() # for the first spouse, this is true. # For subsequent spouses, make it false is_first_family = True for family_handle in family_handle_list: child_family = self.database.get_family_from_handle( family_handle) self.doc.write_text_citation( self.__narrator.get_married_string( child_family, is_first_family, self._name_display)) is_first_family = False self.doc.end_paragraph() def write_family_events(self, family): """ write the family events """ if not family.get_event_ref_list(): return mother_handle = family.get_mother_handle() if mother_handle: mother = self._db.get_person_from_handle(mother_handle) mother_name = self._nd.display(mother) if not mother_name: mother_name = self._("Unknown") else: mother_name = self._("Unknown") father_handle = family.get_father_handle() if father_handle: father = self._db.get_person_from_handle(father_handle) father_name = self._nd.display(father) if not father_name: father_name = self._("Unknown") else: father_name = self._("Unknown") first = True for event_ref in family.get_event_ref_list(): if first: self.doc.start_paragraph('DAR-MoreHeader') self.doc.write_text( self._('More about %(mother_name)s and %(father_name)s:') % { 'mother_name': mother_name, 'father_name': father_name }) self.doc.end_paragraph() first = False self.write_event(event_ref) def write_mate(self, person): """Output birth, death, parentage, marriage and notes information """ ind = None has_info = False for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) ind_handle = None if person.get_gender() == Person.MALE: ind_handle = family.get_mother_handle() else: ind_handle = family.get_father_handle() if ind_handle: ind = self._db.get_person_from_handle(ind_handle) for event_ref in ind.get_primary_event_ref_list(): event = self._db.get_event_from_handle(event_ref.ref) if event: etype = event.get_type() if (etype == EventType.BAPTISM or etype == EventType.BURIAL or etype == EventType.BIRTH or etype == EventType.DEATH): has_info = True break if not has_info: family_handle = ind.get_main_parents_family_handle() if family_handle: fam = self._db.get_family_from_handle(family_handle) if fam.get_mother_handle() or fam.get_father_handle(): has_info = True break if has_info: self.doc.start_paragraph("DAR-MoreHeader") plist = ind.get_media_list() if self.addimages and len(plist) > 0: photo = plist[0] utils.insert_image(self._db, self.doc, photo, self._user) name = self._nd.display(ind) if not name: name = self._("Unknown") mark = utils.get_person_mark(self._db, ind) if family.get_relationship() == FamilyRelType.MARRIED: self.doc.write_text(self._("Spouse: %s") % name, mark) else: self.doc.write_text( self._("Relationship with: %s") % name, mark) if name[-1:] != '.': self.doc.write_text(".") if self.want_ids: self.doc.write_text(' (%s)' % ind.get_gramps_id()) self.doc.write_text_citation(self.endnotes(ind)) self.doc.end_paragraph() self.doc.start_paragraph("DAR-Entry") self.__narrator.set_subject(ind) text = self.__narrator.get_born_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_baptised_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_christened_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_died_string(self.calcageflag) if text: self.doc.write_text_citation(text) text = self.__narrator.get_buried_string() if text: self.doc.write_text_citation(text) self.write_parents(ind) self.doc.end_paragraph() def endnotes(self, obj): """ cite the endnotes for the object """ if not obj or not self.inc_sources: return "" txt = endnotes.cite_source(self.bibli, self._db, obj, self._locale) if txt: txt = '<super>' + txt + '</super>' return txt
class AncestorReport(Report): """ Ancestor Report class """ def __init__(self, database, options, user): """ Create the AncestorReport object that produces the Ahnentafel report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. gen - Maximum number of generations to include. pagebbg - Whether to include page breaks between generations. name_format - Preferred format to display names incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) self.map = {} menu = options.menu lang = menu.get_option_by_name('trans').get_value() rlocale = self.set_locale(lang) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, rlocale) self.database = CacheProxyDb(self.database) self.max_generations = menu.get_option_by_name('maxgen').get_value() self.pgbrk = menu.get_option_by_name('pagebbg').get_value() self.opt_namebrk = menu.get_option_by_name('namebrk').get_value() pid = menu.get_option_by_name('pid').get_value() self.center_person = self.database.get_person_from_gramps_id(pid) if (self.center_person == None): raise ReportError(_("Person %s is not in the Database") % pid) stdoptions.run_name_format_option(self, menu) self.__narrator = Narrator(self.database, use_fulldate=True, nlocale=rlocale) def apply_filter(self, person_handle, index, generation=1): """ Recursive function to walk back all parents of the current person. When max_generations are hit, we stop the traversal. """ # check for end of the current recursion level. This happens # if the person handle is None, or if the max_generations is hit if not person_handle or generation > self.max_generations: return # store the person in the map based off their index number # which is passed to the routine. self.map[index] = person_handle # retrieve the Person instance from the database from the # passed person_handle and find the parents from the list. # Since this report is for natural parents (birth parents), # we have to handle that parents may not person = self.database.get_person_from_handle(person_handle) if person is None: return father_handle = None mother_handle = None for family_handle in person.get_parent_family_handle_list(): family = self.database.get_family_from_handle(family_handle) # filter the child_ref_list to find the reference that matches # the passed person. There should be exactly one, but there is # nothing that prevents the same child in the list multiple times. ref = [ c for c in family.get_child_ref_list() if c.get_reference_handle() == person_handle ] if ref: # If the father_handle is not defined and the relationship is # BIRTH, then we have found the birth father. Same applies to # the birth mother. If for some reason, the we have multiple # people defined as the birth parents, we will select based on # priority in the list if not father_handle and \ ref[0].get_father_relation() == ChildRefType.BIRTH: father_handle = family.get_father_handle() if not mother_handle and \ ref[0].get_mother_relation() == ChildRefType.BIRTH: mother_handle = family.get_mother_handle() # Recursively call the function. It is okay if the handle is None, # since routine handles a handle of None self.apply_filter(father_handle, index * 2, generation + 1) self.apply_filter(mother_handle, (index * 2) + 1, generation + 1) def write_report(self): """ The routine the actually creates the report. At this point, the document is opened and ready for writing. """ # Call apply_filter to build the self.map array of people in the # database that match the ancestry. self.apply_filter(self.center_person.get_handle(), 1) # Write the title line. Set in INDEX marker so that this section will be # identified as a major category if this is included in a Book report. name = self._name_display.display_formal(self.center_person) # feature request 2356: avoid genitive form title = self._("Ahnentafel Report for %s") % name mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.start_paragraph("AHN-Title") self.doc.write_text(title, mark) self.doc.end_paragraph() # get the entries out of the map, and sort them. generation = 0 for key in sorted(self.map): # check the index number to see if we need to start a new generation if generation == log2(key): # generate a page break if requested if self.pgbrk and generation > 0: self.doc.page_break() generation += 1 # Create the Generation title, set an index marker gen_text = self._("Generation %d") % generation mark = None # don't need any with no page breaks if self.pgbrk: mark = IndexMark(gen_text, INDEX_TYPE_TOC, 2) self.doc.start_paragraph("AHN-Generation") self.doc.write_text(gen_text, mark) self.doc.end_paragraph() # Build the entry self.doc.start_paragraph("AHN-Entry", "%d." % key) person = self.database.get_person_from_handle(self.map[key]) if person is None: continue name = self._name_display.display(person) mark = utils.get_person_mark(self.database, person) # write the name in bold self.doc.start_bold() self.doc.write_text(name.strip(), mark) self.doc.end_bold() # terminate with a period if it is not already terminated. # This can happen if the person's name ends with something 'Jr.' if name[-1:] == '.': self.doc.write_text(" ") else: self.doc.write_text(". ") # Add a line break if requested (not implemented yet) if self.opt_namebrk: self.doc.write_text('\n') self.__narrator.set_subject(person) self.doc.write_text(self.__narrator.get_born_string()) self.doc.write_text(self.__narrator.get_baptised_string()) self.doc.write_text(self.__narrator.get_christened_string()) self.doc.write_text(self.__narrator.get_died_string()) self.doc.write_text(self.__narrator.get_buried_string()) self.doc.end_paragraph()
class EndOfLineReport(Report): """ EndOfLine Report """ def __init__(self, database, options, user): """ Create the EndOfLineReport object that produces the report. The arguments are: database - the GRAMPS database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. name_format - Preferred format to display names incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) menu = options.menu lang = menu.get_option_by_name('trans').get_value() rlocale = self.set_locale(lang) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, rlocale) self.database = CacheProxyDb(self.database) pid = menu.get_option_by_name('pid').get_value() self.center_person = self.database.get_person_from_gramps_id(pid) if self.center_person is None: raise ReportError(_("Person %s is not in the Database") % pid) stdoptions.run_name_format_option(self, menu) # eol_map is a map whose: # keys are the generations of the people # values are a map whose: # keys are person handles # values are an array whose: # elements are an array of ancestor person handles that link # the eol person handle to the person or interest # eol_map[generation][person_handle][pedigree_idx][ancestor_handle_idx] # # There is an array of pedigrees because one person could show up twice # in one generation (descendants marrying). Most people only have one # pedigree. # # eol_map is populated by get_eol() which calls itself recursively. self.eol_map = {} self.get_eol(self.center_person, 1, []) def get_eol(self, person, gen, pedigree): """ Recursively find the end of the line for each person """ person_handle = person.get_handle() new_pedigree = list(pedigree) + [person_handle] person_is_eol = False families = person.get_parent_family_handle_list() if person_handle in pedigree: # This is a severe error! # It indicates a loop in ancestry: A -> B -> A person_is_eol = True elif not families: person_is_eol = True else: for family_handle in families: family = self.database.get_family_from_handle(family_handle) father_handle = family.get_father_handle() mother_handle = family.get_mother_handle() if father_handle: father = self.database.get_person_from_handle( father_handle) self.get_eol(father, gen + 1, new_pedigree) if mother_handle: mother = self.database.get_person_from_handle( mother_handle) self.get_eol(mother, gen + 1, new_pedigree) if not father_handle or not mother_handle: person_is_eol = True if person_is_eol: # This person is the end of a line if gen not in self.eol_map: self.eol_map[gen] = {} if person_handle not in self.eol_map[gen]: self.eol_map[gen][person_handle] = [] self.eol_map[gen][person_handle].append(new_pedigree) def write_report(self): """ The routine that actually creates the report. At this point, the document is opened and ready for writing. """ pname = self._name_display.display(self.center_person) self.doc.start_paragraph("EOL-Title") # feature request 2356: avoid genitive form title = self._("End of Line Report for %s") % pname mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() self.doc.start_paragraph("EOL-Subtitle") # feature request 2356: avoid genitive form title = self._( "All the ancestors of %s who are missing a parent") % pname self.doc.write_text(title) self.doc.end_paragraph() self.doc.start_table('EolTable', 'EOL-Table') for generation, handles in self.eol_map.items(): self.write_generation_row(generation) for person_handle, pedigrees in handles.items(): self.write_person_row(person_handle) list(map(self.write_pedigree_row, pedigrees)) self.doc.end_table() def write_generation_row(self, generation): """ Write out a row in the table showing the generation. """ self.doc.start_row() self.doc.start_cell('EOL_GenerationCell', 2) self.doc.start_paragraph('EOL-Generation') self.doc.write_text(self._("Generation %d") % generation) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() def write_person_row(self, person_handle): """ Write a row in the table with information about the given person. """ person = self.database.get_person_from_handle(person_handle) name = self._name_display.display(person) mark = utils.get_person_mark(self.database, person) birth_date = "" birth_ref = person.get_birth_ref() if birth_ref: event = self.database.get_event_from_handle(birth_ref.ref) birth_date = self._get_date(event.get_date_object()) death_date = "" death_ref = person.get_death_ref() if death_ref: event = self.database.get_event_from_handle(death_ref.ref) death_date = self._get_date(event.get_date_object()) dates = '' if birth_date or death_date: dates = self._(" (%(birth_date)s - %(death_date)s)") % { 'birth_date': birth_date, 'death_date': death_date } self.doc.start_row() self.doc.start_cell('EOL-TableCell', 2) self.doc.start_paragraph('EOL-Normal') self.doc.write_text(name, mark) self.doc.write_text(dates) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() def write_pedigree_row(self, pedigree): """ Write a row in the table with with the person's family line. pedigree is an array containing the names of the people in the pedigree """ names = [] for person_handle in pedigree: person = self.database.get_person_from_handle(person_handle) names.append(self._name_display.display(person)) text = " -- ".join(names) self.doc.start_row() self.doc.start_cell('EOL-TableCell') self.doc.end_cell() self.doc.start_cell('EOL-TableCell') self.doc.start_paragraph('EOL-Pedigree') self.doc.write_text(text) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row()
class RelGraphReport(Report): def __init__(self, database, options, user): """ Create RelGraphReport object that produces the report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. filter - Filter to be applied to the people of the database. The option class carries its number, and the function returning the list of filters. arrow - Arrow styles for heads and tails. showfamily - Whether to show family nodes. inc_id - Whether to include IDs. url - Whether to include URLs. inclimg - Include images or not imgpos - Image position, above/beside name color - Whether to use outline, colored outline or filled color in graph color_males - Colour to apply to males color_females - Colour to apply to females color_unknown - Colour to apply to unknown genders color_families - Colour to apply to families dashed - Whether to use dashed lines for non-birth relationships use_roundedcorners - Whether to use rounded corners for females name_format - Preferred format to display names incl_private - Whether to include private data event_choice - Whether to include dates and/or places occupation - Whether to include occupations living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) menu = options.menu get_option_by_name = options.menu.get_option_by_name get_value = lambda name: get_option_by_name(name).get_value() self.set_locale(menu.get_option_by_name('trans').get_value()) stdoptions.run_date_format_option(self, menu) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, self._locale) self.database = CacheProxyDb(self.database) self._db = self.database self.includeid = get_value('inc_id') self.includeurl = get_value('url') self.includeimg = get_value('includeImages') self.imgpos = get_value('imageOnTheSide') self.use_roundedcorners = get_value('useroundedcorners') self.adoptionsdashed = get_value('dashed') self.show_families = get_value('showfamily') self.use_subgraphs = get_value('usesubgraphs') self.event_choice = get_value('event_choice') self.occupation = get_value('occupation') self.use_html_output = False self.colorize = get_value('color') color_males = get_value('colormales') color_females = get_value('colorfemales') color_unknown = get_value('colorunknown') color_families = get_value('colorfamilies') self.colors = { 'male': color_males, 'female': color_females, 'unknown': color_unknown, 'family': color_families } arrow_str = get_value('arrow') if 'd' in arrow_str: self.arrowheadstyle = 'normal' else: self.arrowheadstyle = 'none' if 'a' in arrow_str: self.arrowtailstyle = 'normal' else: self.arrowtailstyle = 'none' filter_option = get_option_by_name('filter') self._filter = filter_option.get_filter() stdoptions.run_name_format_option(self, menu) pid = get_value('pid') self.center_person = self._db.get_person_from_gramps_id(pid) if self.center_person is None: raise ReportError(_("Person %s is not in the Database") % pid) self.increlname = get_value('increlname') if self.increlname: self.rel_calc = get_relationship_calculator(reinit=True, clocale=self._locale) if __debug__: self.advrelinfo = get_value('advrelinfo') else: self.advrelinfo = False def write_report(self): person_handles = self._filter.apply(self._db, self._db.iter_person_handles(), user=self._user) # Hash people in a dictionary for faster inclusion checking self.persons = set(person_handles) person_handles = self.sort_persons(person_handles) if len(person_handles) > 1: if self._user: self._user.begin_progress(_("Relationship Graph"), _("Generating report"), len(person_handles) * 2) self.add_persons_and_families(person_handles) self.add_child_links_to_families(person_handles) if self._user: self._user.end_progress() def sort_persons(self, person_handle_list): "sort persons by close relations" # first make a list of all persons who don't have any parents root_nodes = list() for person_handle in person_handle_list: person = self.database.get_person_from_handle(person_handle) has_parent = False for parent_handle in find_parents(self.database, person): if parent_handle not in self.persons: continue has_parent = True if not has_parent: root_nodes.append(person_handle) # now start from all root nodes we found and traverse their trees outlist = list() p_done = set() for person_handle in root_nodes: todolist = list() todolist.append(person_handle) while len(todolist) > 0: # take the first person from todolist and do sanity check cur = todolist.pop(0) if cur in p_done: continue if cur not in self.persons: p_done.add(cur) continue person = self.database.get_person_from_handle(cur) # first check whether both parents are added missing_parents = False for parent_handle in find_parents(self.database, person): if not parent_handle or parent_handle in p_done: continue if parent_handle not in self.persons: continue todolist.insert(0, parent_handle) missing_parents = True # if one of the parents is still missing, wait for them if missing_parents: continue # add person to the sorted output outlist.append(cur) p_done.add(cur) # add all spouses and children to the todo list family_list = person.get_family_handle_list() for fam_handle in family_list: family = self.database.get_family_from_handle(fam_handle) if family is None: continue if (family.get_father_handle() and family.get_father_handle() != cur): todolist.insert(0, family.get_father_handle()) if (family.get_mother_handle() and family.get_mother_handle() != cur): todolist.insert(0, family.get_mother_handle()) for child_ref in family.get_child_ref_list(): todolist.append(child_ref.ref) # finally store the result assert len(person_handle_list) == len(outlist) return outlist def add_child_links_to_families(self, person_handles): """ returns string of Graphviz edges linking parents to families or children """ for person_handle in person_handles: if self._user: self._user.step_progress() person = self._db.get_person_from_handle(person_handle) p_id = person.get_gramps_id() for fam_handle in person.get_parent_family_handle_list(): family = self._db.get_family_from_handle(fam_handle) father_handle = family.get_father_handle() mother_handle = family.get_mother_handle() sibling = False for child_ref in family.get_child_ref_list(): if child_ref.ref == person_handle: frel = child_ref.frel mrel = child_ref.mrel elif child_ref.ref in self.persons: sibling = True if (self.show_families and ((father_handle and father_handle in self.persons) or (mother_handle and mother_handle in self.persons) or sibling)): # Link to the family node if either parent is in graph self.add_family_link(p_id, family, frel, mrel) else: # Link to the parents' nodes directly, if they are in graph if father_handle and father_handle in self.persons: self.add_parent_link(p_id, father_handle, frel) if mother_handle and mother_handle in self.persons: self.add_parent_link(p_id, mother_handle, mrel) def add_family_link(self, p_id, family, frel, mrel): "Links the child to a family" style = 'solid' adopted = ((int(frel) != ChildRefType.BIRTH) or (int(mrel) != ChildRefType.BIRTH)) # If birth relation to father is NONE, meaning there is no father and # if birth relation to mother is BIRTH then solid line if (int(frel) == ChildRefType.NONE and int(mrel) == ChildRefType.BIRTH): adopted = False if adopted and self.adoptionsdashed: style = 'dotted' self.doc.add_link(family.get_gramps_id(), p_id, style, self.arrowheadstyle, self.arrowtailstyle) def add_parent_link(self, p_id, parent_handle, rel): "Links the child to a parent" style = 'solid' if (int(rel) != ChildRefType.BIRTH) and self.adoptionsdashed: style = 'dotted' parent = self._db.get_person_from_handle(parent_handle) self.doc.add_link(parent.get_gramps_id(), p_id, style, self.arrowheadstyle, self.arrowtailstyle) def add_persons_and_families(self, person_handles): "adds nodes for persons and their families" # variable to communicate with get_person_label self.use_html_output = False # The list of families for which we have output the node, # so we don't do it twice families_done = set() for person_handle in person_handles: if self._user: self._user.step_progress() # determine per person if we use HTML style label if self.includeimg: self.use_html_output = True person = self._db.get_person_from_handle(person_handle) if person is None: continue p_id = person.get_gramps_id() # Output the person's node label = self.get_person_label(person) (shape, style, color, fill) = self.get_gender_style(person) url = "" if self.includeurl: phan = person_handle dirpath = "ppl/%s/%s" % (phan[-1], phan[-2]) dirpath = dirpath.lower() url = "%s/%s.html" % (dirpath, phan) self.doc.add_node(p_id, label, shape, color, style, fill, url) # Output families where person is a parent if self.show_families: family_list = person.get_family_handle_list() for fam_handle in family_list: family = self._db.get_family_from_handle(fam_handle) if family is None: continue if fam_handle not in families_done: families_done.add(fam_handle) self.__add_family(fam_handle) # If subgraphs are not chosen then each parent is linked # separately to the family. This gives Graphviz greater # control over the layout of the whole graph but # may leave spouses not positioned together. if not self.use_subgraphs: self.doc.add_link(p_id, family.get_gramps_id(), "", self.arrowheadstyle, self.arrowtailstyle) # Output families where person is a sibling if another sibling # is present family_list = person.get_parent_family_handle_list() for fam_handle in family_list: if fam_handle in families_done: continue family = self.database.get_family_from_handle(fam_handle) if family is None: continue for child_ref in family.get_child_ref_list(): if (child_ref.ref != person_handle and child_ref.ref in self.persons): families_done.add(fam_handle) self.__add_family(fam_handle) def __add_family(self, fam_handle): """Add a node for a family and optionally link the spouses to it""" fam = self._db.get_family_from_handle(fam_handle) if fam is None: return fam_id = fam.get_gramps_id() m_type = m_date = m_place = "" d_type = d_date = d_place = "" for event_ref in fam.get_event_ref_list(): event = self._db.get_event_from_handle(event_ref.ref) if event is None: continue if (event.type == EventType.MARRIAGE and (event_ref.get_role() == EventRoleType.FAMILY or event_ref.get_role() == EventRoleType.PRIMARY)): m_type = event.type m_date = self.get_date_string(event) if not (self.event_choice == 3 and m_date): m_place = self.get_place_string(event) break if (event.type == EventType.DIVORCE and (event_ref.get_role() == EventRoleType.FAMILY or event_ref.get_role() == EventRoleType.PRIMARY)): d_type = event.type d_date = self.get_date_string(event) if not (self.event_choice == 3 and d_date): d_place = self.get_place_string(event) break labellines = list() if self.includeid == 2: # id on separate line labellines.append("(%s)" % fam_id) if self.event_choice == 7: if m_type: line = m_type.get_abbreviation() if m_date: line += ' %s' % m_date if m_date and m_place: labellines.append(line) line = '' if m_place: line += ' %s' % m_place labellines.append(line) if d_type: line = d_type.get_abbreviation() if d_date: line += ' %s' % d_date if d_date and d_place: labellines.append(line) line = '' if d_place: line += ' %s' % d_place labellines.append(line) else: if m_date: labellines.append("(%s)" % m_date) if m_place: labellines.append("(%s)" % m_place) label = "\\n".join(labellines) labellines = list() if self.includeid == 1: # id on same line labellines.append("(%s)" % fam_id) if len(label): labellines.append(label) label = ' '.join(labellines) color = "" fill = "" style = "solid" if self.colorize == 'colored': color = self.colors['family'] elif self.colorize == 'filled': fill = self.colors['family'] style = "filled" self.doc.add_node(fam_id, label, "ellipse", color, style, fill) # If subgraphs are used then we add both spouses here and Graphviz # will attempt to position both spouses closely together. # TODO: A person who is a parent in more than one family may only be # positioned next to one of their spouses. The code currently # does not take into account multiple spouses. if self.use_subgraphs: self.doc.start_subgraph(fam_id) f_handle = fam.get_father_handle() m_handle = fam.get_mother_handle() if f_handle: father = self._db.get_person_from_handle(f_handle) self.doc.add_link(father.get_gramps_id(), fam_id, "", self.arrowheadstyle, self.arrowtailstyle) if m_handle: mother = self._db.get_person_from_handle(m_handle) self.doc.add_link(mother.get_gramps_id(), fam_id, "", self.arrowheadstyle, self.arrowtailstyle) self.doc.end_subgraph() def get_gender_style(self, person): "return gender specific person style" gender = person.get_gender() shape = "box" style = "solid" color = "" fill = "" if gender == person.FEMALE and self.use_roundedcorners: style = "rounded" elif gender == person.UNKNOWN: shape = "hexagon" if person == self.center_person and self.increlname: shape = "octagon" if self.colorize == 'colored': if gender == person.MALE: color = self.colors['male'] elif gender == person.FEMALE: color = self.colors['female'] else: color = self.colors['unknown'] elif self.colorize == 'filled': style += ",filled" if gender == person.MALE: fill = self.colors['male'] elif gender == person.FEMALE: fill = self.colors['female'] else: fill = self.colors['unknown'] return(shape, style, color, fill) def get_person_label(self, person): "return person label string" # see if we have an image to use for this person image_path = None if self.use_html_output: media_list = person.get_media_list() if len(media_list) > 0: media_handle = media_list[0].get_reference_handle() media = self._db.get_media_from_handle(media_handle) media_mime_type = media.get_mime_type() if media_mime_type[0:5] == "image": image_path = get_thumbnail_path( media_path_full(self._db, media.get_path()), rectangle=media_list[0].get_rectangle()) # test if thumbnail actually exists in thumbs # (import of data means media files might not be present image_path = find_file(image_path) label = "" line_delimiter = '\\n' # If we have an image, then start an HTML table; remember to close # the table afterwards! # # This isn't a free-form HTML format here...just a few keywords that # happen to be # similar to keywords commonly seen in HTML. For additional # information on what # is allowed, see: # # http://www.graphviz.org/info/shapes.html#html # if self.use_html_output and image_path: line_delimiter = '<BR/>' label += '<TABLE BORDER="0" CELLSPACING="2" CELLPADDING="0" ' label += 'CELLBORDER="0"><TR><TD></TD><TD>' label += '<IMG SRC="%s"/></TD><TD></TD>' % image_path if self.imgpos == 0: #trick it into not stretching the image label += '</TR><TR><TD COLSPAN="3">' else: label += '<TD>' else: #no need for html label with this person self.use_html_output = False # at the very least, the label must have the person's name p_name = self._name_display.display(person) p_name = p_name.replace('"', '"') label += p_name.replace('<', '<').replace('>', '>') p_id = person.get_gramps_id() if self.includeid == 1: # same line label += " (%s)" % p_id elif self.includeid == 2: # own line label += "%s(%s)" % (line_delimiter, p_id) if self.event_choice != 0: b_date, d_date, b_place, d_place, b_type, d_type = \ self.get_event_strings(person) if self.event_choice in [1, 2, 3, 4, 5] and (b_date or d_date): label += '%s(' % line_delimiter if b_date: label += '%s' % b_date label += ' – ' if d_date: label += '%s' % d_date label += ')' if (self.event_choice in [2, 3, 5, 6] and (b_place or d_place) and not (self.event_choice == 3 and (b_date or d_date)) ): label += '%s(' % line_delimiter if b_place: label += '%s' % b_place label += ' – ' if d_place: label += '%s' % d_place label += ')' if self.event_choice == 7: if b_type: label += '%s%s' % (line_delimiter, b_type.get_abbreviation()) if b_date: label += ' %s' % b_date if b_place: label += ' %s' % b_place if d_type: label += '%s%s' % (line_delimiter, d_type.get_abbreviation()) if d_date: label += ' %s' % d_date if d_place: label += ' %s' % d_place if self.increlname and self.center_person != person: # display relationship info if self.advrelinfo: (relationship, _ga, _gb) = self.rel_calc.get_one_relationship( self._db, self.center_person, person, extra_info=True, olocale=self._locale) if relationship: label += "%s(%s Ga=%d Gb=%d)" % (line_delimiter, relationship, _ga, _gb) else: relationship = self.rel_calc.get_one_relationship( self._db, self.center_person, person, olocale=self._locale) if relationship: label += "%s(%s)" % (line_delimiter, relationship) if self.occupation > 0: event_refs = person.get_primary_event_ref_list() events = [event for event in [self._db.get_event_from_handle(ref.ref) for ref in event_refs] if event.get_type() == EventType(EventType.OCCUPATION)] if len(events) > 0: events.sort(key=lambda x: x.get_date_object()) if self.occupation == 1: occupation = events[-1].get_description() if occupation: label += "%s(%s)" % (line_delimiter, occupation) elif self.occupation == 2: for evt in events: date = self.get_date_string(evt) place = self.get_place_string(evt) desc = evt.get_description() if not date and not desc and not place: continue label += '%s(' % line_delimiter if date: label += '%s' % date if desc: label += ' ' if desc: label += '%s' % desc if place: if date or desc: label += self._(', ') # Arabic OK label += '%s' % place label += ')' # see if we have a table that needs to be terminated if self.use_html_output: label += '</TD></TR></TABLE>' return label else: # non html label is enclosed by "" so escape other " return label.replace('"', '\\\"') def get_event_strings(self, person): "returns tuple of birth/christening and death/burying date strings" birth_date = birth_place = death_date = death_place = "" birth_type = death_type = "" birth_event = get_birth_or_fallback(self._db, person) if birth_event: birth_type = birth_event.type birth_date = self.get_date_string(birth_event) birth_place = self.get_place_string(birth_event) death_event = get_death_or_fallback(self._db, person) if death_event: death_type = death_event.type death_date = self.get_date_string(death_event) death_place = self.get_place_string(death_event) return (birth_date, death_date, birth_place, death_place, birth_type, death_type) def get_date_string(self, event): """ return date string for an event label. Based on the data availability and preferences, we select one of the following for a given event: year only complete date empty string """ if event and event.get_date_object() is not None: event_date = event.get_date_object() if event_date.get_year_valid(): if self.event_choice in [4, 5]: return '%i' % event_date.get_year() elif self.event_choice in [1, 2, 3, 7]: return self._get_date(event_date) return '' def get_place_string(self, event): """ return place string for an event label. Based on the data availability and preferences, we select one of the following for a given event: place name empty string """ if event and self.event_choice in [2, 3, 5, 6, 7]: place = _pd.display_event(self._db, event) return place.replace('<', '<').replace('>', '>') return ''
class DetAncestorReport(Report): """ the Detailed Ancestor Report """ def __init__(self, database, options, user): """ Create the DetAncestorReport object that produces the report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. gen - Maximum number of generations to include. inc_id - Whether to include Gramps IDs pagebgg - Whether to include page breaks between generations. pageben - Whether to include page break before End Notes. firstName - Whether to use first names instead of pronouns. fulldate - Whether to use full dates instead of just year. listchildren - Whether to list children. list_children_spouses - Whether to list the spouses of the children includenotes - Whether to include notes. incattrs - Whether to include attributes blankplace - Whether to replace missing Places with ___________. blankDate - Whether to replace missing Dates with ___________. calcageflag - Whether to compute age. dupperson - Whether to omit duplicate ancestors (e.g. when distant cousins marry). verbose - Whether to use complete sentences childref - Whether to add descendant references in child list. addimages - Whether to include images. pid - The Gramps ID of the center person for the report. name_format - Preferred format to display names other_events - Whether to include other events. incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) self.map = {} self._user = user menu = options.menu get_option_by_name = menu.get_option_by_name get_value = lambda name: get_option_by_name(name).get_value() self.set_locale(menu.get_option_by_name('trans').get_value()) stdoptions.run_date_format_option(self, menu) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, self._locale) self.database = CacheProxyDb(self.database) self._db = self.database self.max_generations = get_value('gen') self.pgbrk = get_value('pagebbg') self.pgbrkenotes = get_value('pageben') self.fulldate = get_value('fulldates') use_fulldate = self.fulldate self.listchildren = get_value('listc') self.list_children_spouses = get_value('listc_spouses') self.includenotes = get_value('incnotes') use_call = get_value('usecall') blankplace = get_value('repplace') blankdate = get_value('repdate') self.calcageflag = get_value('computeage') self.dupperson = get_value('omitda') self.verbose = get_value('verbose') self.childref = get_value('desref') self.addimages = get_value('incphotos') self.inc_names = get_value('incnames') self.inc_events = get_value('incevents') self.inc_addr = get_value('incaddresses') self.inc_sources = get_value('incsources') self.inc_srcnotes = get_value('incsrcnotes') self.inc_attrs = get_value('incattrs') self.initial_sosa = get_value('initial_sosa') self.want_ids = get_value('inc_id') pid = get_value('pid') self.other_events = get_value('incotherevents') self.center_person = self._db.get_person_from_gramps_id(pid) if self.center_person is None: raise ReportError(_("Person %s is not in the Database") % pid) stdoptions.run_name_format_option(self, menu) self._nd = self._name_display self.gen_handles = {} self.prev_gen_handles = {} if blankdate: empty_date = EMPTY_ENTRY else: empty_date = "" if blankplace: empty_place = EMPTY_ENTRY else: empty_place = "" self.__narrator = Narrator(self._db, self.verbose, use_call, use_fulldate, empty_date, empty_place, nlocale=self._locale, get_endnote_numbers=self.endnotes) self.bibli = Bibliography(Bibliography.MODE_DATE|Bibliography.MODE_PAGE) def apply_filter(self, person_handle, index): """ recurse up through the generations """ if (not person_handle) or (index >= 2**self.max_generations): return self.map[index] = person_handle person = self._db.get_person_from_handle(person_handle) family_handle = person.get_main_parents_family_handle() if family_handle: family = self._db.get_family_from_handle(family_handle) self.apply_filter(family.get_father_handle(), index*2) self.apply_filter(family.get_mother_handle(), (index*2)+1) def write_report(self): self.apply_filter(self.center_person.get_handle(), 1) name = self._nd.display_name(self.center_person.get_primary_name()) if not name: name = self._("Unknown") self.doc.start_paragraph("DAR-Title") # feature request 2356: avoid genitive form title = self._("Ancestral Report for %s") % name mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() generation = 0 for key in sorted(self.map): if generation == 0 or key >= 2**generation: if self.pgbrk and generation > 0: self.doc.page_break() self.doc.start_paragraph("DAR-Generation") text = self._("Generation %d") % (generation+1) mark = IndexMark(text, INDEX_TYPE_TOC, 2) self.doc.write_text(text, mark) self.doc.end_paragraph() generation += 1 if self.childref: self.prev_gen_handles = self.gen_handles.copy() self.gen_handles.clear() person_handle = self.map[key] person = self._db.get_person_from_handle(person_handle) self.gen_handles[person_handle] = key dupperson = self.write_person(key) if dupperson == 0: # Is this a duplicate ind record if self.listchildren or self.inc_events: for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) mother_handle = family.get_mother_handle() if (mother_handle is None or mother_handle not in iter(self.map.values()) or person.get_gender() == Person.FEMALE): # The second test above also covers the 1. person's # mate, which is not an ancestor and as such is not # included in the self.map dictionary if self.listchildren: self.write_children(family) if self.inc_events: self.write_family_events(family) if self.inc_sources: if self.pgbrkenotes: self.doc.page_break() # it ignores language set for Note type (use locale) endnotes.write_endnotes(self.bibli, self._db, self.doc, printnotes=self.inc_srcnotes, elocale=self._locale) def _get_s_s(self, key): """returns Sosa-Stradonitz (a.k.a. Kekule or Ahnentafel) number""" generation = int(math.floor(math.log(key, 2))) # 0 gen_start = pow(2, generation) # 1 new_gen_start = self.initial_sosa * gen_start # 3 return new_gen_start + (key - gen_start) # 3+0 def write_person(self, key): """ Output birth, death, parentage, marriage and notes information """ def write_more_header(first, name): """ convenience function """ if first: self.doc.start_paragraph('DAR-MoreHeader') self.doc.write_text(self._('More about %(person_name)s:' ) % {'person_name' : name}) self.doc.end_paragraph() return False person_handle = self.map[key] person = self._db.get_person_from_handle(person_handle) plist = person.get_media_list() self.__narrator.set_subject(person) if self.addimages and len(plist) > 0: photo = plist[0] utils.insert_image(self._db, self.doc, photo, self._user) self.doc.start_paragraph("DAR-First-Entry", "%d." % self._get_s_s(key)) name = self._nd.display(person) if not name: name = self._("Unknown") mark = utils.get_person_mark(self._db, person) self.doc.start_bold() self.doc.write_text(name, mark) if name[-1:] == '.': self.doc.write_text_citation("%s " % self.endnotes(person)) elif name: self.doc.write_text_citation("%s. " % self.endnotes(person)) self.doc.end_bold() if self.want_ids: self.doc.write_text('(%s) ' % person.get_gramps_id()) if self.dupperson: # Check for duplicate record (result of distant cousins marrying) for dkey in sorted(self.map): if dkey >= key: break if self.map[key] == self.map[dkey]: self.doc.write_text( self._("%(name)s is the same person as [%(id_str)s]." ) % {'name' : '', 'id_str' : str(dkey)}) self.doc.end_paragraph() return 1 # Duplicate person if not self.verbose: self.write_parents(person) text = self.__narrator.get_born_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_baptised_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_christened_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_died_string(self.calcageflag) if text: self.doc.write_text_citation(text) text = self.__narrator.get_buried_string() if text: self.doc.write_text_citation(text) if self.verbose: self.write_parents(person) if not key % 2 or key == 1: self.write_marriage(person) self.doc.end_paragraph() if key == 1: self.write_mate(person) notelist = person.get_note_list() if len(notelist) > 0 and self.includenotes: self.doc.start_paragraph("DAR-NoteHeader") # feature request 2356: avoid genitive form self.doc.write_text(self._("Notes for %s") % name) self.doc.end_paragraph() for notehandle in notelist: note = self._db.get_note_from_handle(notehandle) self.doc.write_styled_note( note.get_styledtext(), note.get_format(), "DAR-Entry", contains_html=(note.get_type() == NoteType.HTML_CODE) ) first = True if self.inc_names: for alt_name in person.get_alternate_names(): first = write_more_header(first, name) self.doc.start_paragraph('DAR-MoreDetails') atype = self._get_type(alt_name.get_type()) self.doc.write_text_citation( self._('%(type)s: %(value)s%(endnotes)s' ) % {'type' : self._(atype), 'value' : alt_name.get_regular_name(), 'endnotes' : self.endnotes(alt_name)}) self.doc.end_paragraph() if self.inc_events: birth_ref = person.get_birth_ref() death_ref = person.get_death_ref() for event_ref in person.get_primary_event_ref_list(): if event_ref == birth_ref or event_ref == death_ref: continue first = write_more_header(first, name) self.write_event(event_ref) if self.other_events: for event_ref in person.get_event_ref_list(): role = event_ref.get_role() if role in (EventRoleType.PRIMARY, EventRoleType.FAMILY): continue first = write_more_header(first, name) self.write_event(event_ref) if self.inc_addr: for addr in person.get_address_list(): first = write_more_header(first, name) self.doc.start_paragraph('DAR-MoreDetails') text = utils.get_address_str(addr) self.doc.write_text(self._('Address: ')) if self.fulldate: date = self._get_date(addr.get_date_object()) else: date = addr.get_date_object().get_year() if date: # translators: needed for Arabic, ignore otherwise self.doc.write_text(self._('%s, ') % date) self.doc.write_text(text) self.doc.write_text_citation(self.endnotes(addr)) self.doc.end_paragraph() if self.inc_attrs: attrs = person.get_attribute_list() if attrs: first = write_more_header(first, name) for attr in attrs: self.doc.start_paragraph('DAR-MoreDetails') attr_name = attr.get_type().type2base() # translators: needed for French, ignore otherwise text = self._("%(type)s: %(value)s%(endnotes)s" ) % {'type' : self._(attr_name), 'value' : attr.get_value(), 'endnotes' : self.endnotes(attr)} self.doc.write_text_citation(text) self.doc.end_paragraph() return 0 # Not duplicate person def write_event(self, event_ref): """ write out the event """ text = "" event = self._db.get_event_from_handle(event_ref.ref) if self.fulldate: date = self._get_date(event.get_date_object()) else: date = event.get_date_object().get_year() place = _pd.display_event(self._db, event) self.doc.start_paragraph('DAR-MoreDetails') if date and place: # translators: needed for Arabic, ignore otherwise text += self._('%(str1)s, %(str2)s' ) % {'str1' : date, 'str2' : place} elif date: text += '%s' % date elif place: text += '%s' % self._(place) if event.get_description(): if text: text += ". " text += event.get_description() text += self.endnotes(event) if text: text += ". " event_name = self._(self._get_type(event.get_type())) role = event_ref.get_role() if role in (EventRoleType.PRIMARY, EventRoleType.FAMILY): # translators: needed for French, ignore otherwise text = self._('%(str1)s: %(str2)s' ) % {'str1' : event_name, 'str2' : text} else: primaries = get_participant_from_event(self._db, event_ref.ref) text = self._('%(event_role)s at %(event_name)s ' 'of %(primary_person)s: %(event_text)s' ) % {'event_role' : self._(role.xml_str()), 'event_name' : event_name, 'primary_person' : primaries, 'event_text' : text} self.doc.write_text_citation(text) if self.inc_attrs: text = "" attr_list = event.get_attribute_list() attr_list.extend(event_ref.get_attribute_list()) for attr in attr_list: if text: # translators: needed for Arabic, ignore otherwise text += self._("; ") attr_name = attr.get_type().type2base() # translators: needed for French, ignore otherwise text += self._("%(type)s: %(value)s%(endnotes)s" ) % {'type' : self._(attr_name), 'value' : attr.get_value(), 'endnotes' : self.endnotes(attr)} text = " " + text self.doc.write_text_citation(text) self.doc.end_paragraph() if self.includenotes: # if the event or event reference has a note attached to it, # get the text and format it correctly notelist = event.get_note_list() notelist.extend(event_ref.get_note_list()) for notehandle in notelist: note = self._db.get_note_from_handle(notehandle) self.doc.write_styled_note( note.get_styledtext(), note.get_format(), "DAR-MoreDetails", contains_html=(note.get_type() == NoteType.HTML_CODE) ) def write_parents(self, person): """ write the parents """ family_handle = person.get_main_parents_family_handle() if family_handle: family = self._db.get_family_from_handle(family_handle) mother_handle = family.get_mother_handle() father_handle = family.get_father_handle() if mother_handle: mother = self._db.get_person_from_handle(mother_handle) mother_name = self._nd.display_name(mother.get_primary_name()) mother_mark = utils.get_person_mark(self._db, mother) else: mother_name = "" mother_mark = "" if father_handle: father = self._db.get_person_from_handle(father_handle) father_name = self._nd.display_name(father.get_primary_name()) father_mark = utils.get_person_mark(self._db, father) else: father_name = "" father_mark = "" text = self.__narrator.get_child_string(father_name, mother_name) if text: self.doc.write_text(text) if father_mark: self.doc.write_text("", father_mark) if mother_mark: self.doc.write_text("", mother_mark) def write_marriage(self, person): """ Output marriage sentence. """ is_first = True for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) spouse_handle = utils.find_spouse(person, family) if spouse_handle: spouse = self._db.get_person_from_handle(spouse_handle) spouse_mark = utils.get_person_mark(self._db, spouse) else: spouse_mark = None text = self.__narrator.get_married_string(family, is_first, self._nd) if text: self.doc.write_text_citation(text, spouse_mark) if self.want_ids: self.doc.write_text(' (%s)' % family.get_gramps_id()) is_first = False def write_children(self, family): """ List children. :param family: Family :return: """ if not family.get_child_ref_list(): return mother_handle = family.get_mother_handle() if mother_handle: mother = self._db.get_person_from_handle(mother_handle) mother_name = self._nd.display(mother) if not mother_name: mother_name = self._("Unknown") else: mother_name = self._("Unknown") father_handle = family.get_father_handle() if father_handle: father = self._db.get_person_from_handle(father_handle) father_name = self._nd.display(father) if not father_name: father_name = self._("Unknown") else: father_name = self._("Unknown") self.doc.start_paragraph("DAR-ChildTitle") self.doc.write_text( self._("Children of %(mother_name)s and %(father_name)s" ) % {'father_name' : father_name, 'mother_name' : mother_name}) self.doc.end_paragraph() cnt = 1 for child_ref in family.get_child_ref_list(): child_handle = child_ref.ref child = self._db.get_person_from_handle(child_handle) child_name = self._nd.display(child) if not child_name: child_name = self._("Unknown") child_mark = utils.get_person_mark(self._db, child) if self.childref and self.prev_gen_handles.get(child_handle): value = int(self.prev_gen_handles.get(child_handle)) child_name += " [%d]" % self._get_s_s(value) self.doc.start_paragraph("DAR-ChildList", utils.roman(cnt).lower() + ".") cnt += 1 self.__narrator.set_subject(child) if child_name: self.doc.write_text("%s. " % child_name, child_mark) if self.want_ids: self.doc.write_text('(%s) ' % child.get_gramps_id()) self.doc.write_text_citation( self.__narrator.get_born_string() or self.__narrator.get_christened_string() or self.__narrator.get_baptised_string()) self.doc.write_text_citation( self.__narrator.get_died_string() or self.__narrator.get_buried_string()) # if the list_children_spouses option is selected: if self.list_children_spouses: # get the family of the child that contains the spouse # of the child. There may be more than one spouse for each # child family_handle_list = child.get_family_handle_list() # for the first spouse, this is true. # For subsequent spouses, make it false is_first_family = True for family_handle in family_handle_list: child_family = self.database.get_family_from_handle( family_handle ) self.doc.write_text_citation( self.__narrator.get_married_string( child_family, is_first_family, self._name_display ) ) is_first_family = False self.doc.end_paragraph() def write_family_events(self, family): """ write the family events """ if not family.get_event_ref_list(): return mother_handle = family.get_mother_handle() if mother_handle: mother = self._db.get_person_from_handle(mother_handle) mother_name = self._nd.display(mother) if not mother_name: mother_name = self._("Unknown") else: mother_name = self._("Unknown") father_handle = family.get_father_handle() if father_handle: father = self._db.get_person_from_handle(father_handle) father_name = self._nd.display(father) if not father_name: father_name = self._("Unknown") else: father_name = self._("Unknown") first = True for event_ref in family.get_event_ref_list(): if first: self.doc.start_paragraph('DAR-MoreHeader') self.doc.write_text( self._('More about %(mother_name)s and %(father_name)s:' ) % {'mother_name' : mother_name, 'father_name' : father_name}) self.doc.end_paragraph() first = False self.write_event(event_ref) def write_mate(self, person): """Output birth, death, parentage, marriage and notes information """ ind = None has_info = False for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) ind_handle = None if person.get_gender() == Person.MALE: ind_handle = family.get_mother_handle() else: ind_handle = family.get_father_handle() if ind_handle: ind = self._db.get_person_from_handle(ind_handle) for event_ref in ind.get_primary_event_ref_list(): event = self._db.get_event_from_handle(event_ref.ref) if event: etype = event.get_type() if (etype == EventType.BAPTISM or etype == EventType.BURIAL or etype == EventType.BIRTH or etype == EventType.DEATH): has_info = True break if not has_info: family_handle = ind.get_main_parents_family_handle() if family_handle: fam = self._db.get_family_from_handle(family_handle) if fam.get_mother_handle() or fam.get_father_handle(): has_info = True break if has_info: self.doc.start_paragraph("DAR-MoreHeader") plist = ind.get_media_list() if self.addimages and len(plist) > 0: photo = plist[0] utils.insert_image(self._db, self.doc, photo, self._user) name = self._nd.display(ind) if not name: name = self._("Unknown") mark = utils.get_person_mark(self._db, ind) if family.get_relationship() == FamilyRelType.MARRIED: self.doc.write_text(self._("Spouse: %s" ) % name, mark) else: self.doc.write_text(self._("Relationship with: %s" ) % name, mark) if name[-1:] != '.': self.doc.write_text(".") if self.want_ids: self.doc.write_text(' (%s)' % ind.get_gramps_id()) self.doc.write_text_citation(self.endnotes(ind)) self.doc.end_paragraph() self.doc.start_paragraph("DAR-Entry") self.__narrator.set_subject(ind) text = self.__narrator.get_born_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_baptised_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_christened_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_died_string(self.calcageflag) if text: self.doc.write_text_citation(text) text = self.__narrator.get_buried_string() if text: self.doc.write_text_citation(text) self.write_parents(ind) self.doc.end_paragraph() def endnotes(self, obj): """ cite the endnotes for the object """ if not obj or not self.inc_sources: return "" txt = endnotes.cite_source(self.bibli, self._db, obj, self._locale) if txt: txt = '<super>' + txt + '</super>' return txt
class TagReport(Report): """ Tag Report """ def __init__(self, database, options, user): """ Create the TagReport object that produces the report. The arguments are: database - the GRAMPS database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. tag - The tag each object must match to be included. name_format - Preferred format to display names of people incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) menu = options.menu lang = menu.get_option_by_name('trans').get_value() rlocale = self.set_locale(lang) stdoptions.run_private_data_option(self, menu) living_opt = stdoptions.run_living_people_option(self, menu, rlocale) self.database = CacheProxyDb(self.database) self._lv = menu.get_option_by_name('living_people').get_value() for (value, description) in living_opt.get_items(xml_items=True): if value == self._lv: living_desc = self._(description) break self.living_desc = self._("(Living people: %(option_name)s)" ) % {'option_name' : living_desc} self.tag = menu.get_option_by_name('tag').get_value() if not self.tag: raise ReportError( _('Tag Report'), _('You must first create a tag before running this report.')) stdoptions.run_name_format_option(self, menu) def write_report(self): self.doc.start_paragraph("TR-Title") # feature request 2356: avoid genitive form title = self._("Tag Report for %s Items") % self.tag mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() if self._lv != LivingProxyDb.MODE_INCLUDE_ALL: self.doc.start_paragraph("TR-ReportSubtitle") self.doc.write_text(self.living_desc) self.doc.end_paragraph() self.write_people() self.write_families() self.write_events() self.write_places() self.write_notes() self.write_media() self.write_repositories() self.write_sources() self.write_citations() def write_people(self): """ write the people associated with the tag """ plist = self.database.iter_person_handles() filter_class = GenericFilterFactory('Person') a_filter = filter_class() a_filter.add_rule(rules.person.HasTag([self.tag])) ind_list = a_filter.apply(self.database, plist) if not ind_list: return self.doc.start_paragraph("TR-Heading") header = self._("People") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('PeopleTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Name")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Birth")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Death")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for person_handle in ind_list: person = self.database.get_person_from_handle(person_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(person.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() name = self._name_display.display(person) mark = utils.get_person_mark(self.database, person) self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(name, mark) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') birth_ref = person.get_birth_ref() if birth_ref: event = self.database.get_event_from_handle(birth_ref.ref) self.doc.write_text(self._get_date(event.get_date_object())) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') death_ref = person.get_death_ref() if death_ref: event = self.database.get_event_from_handle(death_ref.ref) self.doc.write_text(self._get_date(event.get_date_object())) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_families(self): """ write the families associated with the tag """ flist = self.database.iter_family_handles() filter_class = GenericFilterFactory('Family') a_filter = filter_class() a_filter.add_rule(rules.family.HasTag([self.tag])) fam_list = a_filter.apply(self.database, flist) if not fam_list: return self.doc.start_paragraph("TR-Heading") header = self._("Families") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('FamilyTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Father")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Mother")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Relationship")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for family_handle in fam_list: family = self.database.get_family_from_handle(family_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(family.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') father_handle = family.get_father_handle() if father_handle: father = self.database.get_person_from_handle(father_handle) mark = utils.get_person_mark(self.database, father) self.doc.write_text(self._name_display.display(father), mark) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') mother_handle = family.get_mother_handle() if mother_handle: mother = self.database.get_person_from_handle(mother_handle) mark = utils.get_person_mark(self.database, mother) self.doc.write_text(self._name_display.display(mother), mark) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') relation = family.get_relationship() self.doc.write_text(str(relation)) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_events(self): """ write the events associated with the tag """ elist = self.database.get_event_handles() filter_class = GenericFilterFactory('Event') a_filter = filter_class() a_filter.add_rule(rules.event.HasTag([self.tag])) event_list = a_filter.apply(self.database, elist) if not event_list: return self.doc.start_paragraph("TR-Heading") header = self._("Events") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('EventTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Type")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Participants")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Date")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for event_handle in event_list: event = self.database.get_event_from_handle(event_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(event.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(self._(self._get_type(event.get_type()))) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(get_participant_from_event(self.database, event_handle)) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') date = self._get_date(event.get_date_object()) if date: self.doc.write_text(date) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_places(self): """ write the places associated with the tag """ plist = self.database.get_place_handles() filter_class = GenericFilterFactory('Place') a_filter = filter_class() a_filter.add_rule(rules.place.HasTag([self.tag])) place_list = a_filter.apply(self.database, plist) if not place_list: return self.doc.start_paragraph("TR-Heading") header = self._("Places") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('PlaceTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Title")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Name")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Type")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for place_handle in place_list: place = self.database.get_place_from_handle(place_handle) place_title = _pd.display(self.database, place) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(place.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(place_title) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(place.get_name().get_value()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(str(place.get_type())) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_notes(self): """ write the notes associated with the tag """ nlist = self.database.get_note_handles() filter_class = GenericFilterFactory('Note') a_filter = filter_class() a_filter.add_rule(rules.note.HasTag([self.tag])) note_list = a_filter.apply(self.database, nlist) if not note_list: return self.doc.start_paragraph("TR-Heading") header = self._("Notes") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('NoteTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Type")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell', 2) self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Text")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for note_handle in note_list: note = self.database.get_note_from_handle(note_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(note.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') note_type = note.get_type() self.doc.write_text(str(note_type)) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell', 2) self.doc.write_styled_note( note.get_styledtext(), note.get_format(), 'TR-Note', contains_html=((note.get_type() == NoteType.HTML_CODE))) self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_media(self): """ write the media associated with the tag """ mlist = self.database.get_media_handles(sort_handles=True) filter_class = GenericFilterFactory('Media') a_filter = filter_class() a_filter.add_rule(rules.media.HasTag([self.tag])) media_list = a_filter.apply(self.database, mlist) if not media_list: return self.doc.start_paragraph("TR-Heading") header = self._("Media") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('MediaTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Title")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Type")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Date")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for media_handle in media_list: media = self.database.get_media_from_handle(media_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(media.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') title = media.get_description() self.doc.write_text(str(title)) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') mime_type = media.get_mime_type() self.doc.write_text(str(mime_type)) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') date = self._get_date(media.get_date_object()) if date: self.doc.write_text(date) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_repositories(self): """ write the repositories associated with the tag """ rlist = self.database.get_repository_handles() filter_class = GenericFilterFactory('Repository') a_filter = filter_class() a_filter.add_rule(rules.repository.HasTag([self.tag])) repo_list = a_filter.apply(self.database, rlist) if not repo_list: return self.doc.start_paragraph("TR-Heading") header = self._("Repositories") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('ReopTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Name")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Type")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Email Address")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for repo_handle in repo_list: repo = self.database.get_repository_from_handle(repo_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(repo.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(repo.get_name()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(str(repo.get_type())) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') home_page = '' for url in repo.get_url_list(): if url.get_type() == UrlType.EMAIL: home_page = url.get_path() break self.doc.write_text(home_page) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_sources(self): """ write the sources associated with the tag """ slist = self.database.get_source_handles(sort_handles=True) filter_class = GenericFilterFactory('Source') a_filter = filter_class() a_filter.add_rule(rules.source.HasTag([self.tag])) source_list = a_filter.apply(self.database, slist) if not source_list: return self.doc.start_paragraph("TR-Heading") header = self._("Source") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('SourceTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Title")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Author")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Publication Information")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for source_handle in source_list: source = self.database.get_source_from_handle(source_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(source.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(source.get_title()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(source.get_author()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(source.get_publication_info()) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table() def write_citations(self): """ write the citations associated with the tag """ clist = self.database.get_citation_handles(sort_handles=True) filter_class = GenericFilterFactory('Citation') a_filter = filter_class() a_filter.add_rule(rules.citation.HasTag([self.tag])) citation_list = a_filter.apply(self.database, clist) if not citation_list: return self.doc.start_paragraph("TR-Heading") header = self._("Citations") mark = IndexMark(header, INDEX_TYPE_TOC, 2) self.doc.write_text(header, mark) self.doc.end_paragraph() self.doc.start_table('CitationTable', 'TR-Table') self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Id")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Volume/Page")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Date")) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal-Bold') self.doc.write_text(self._("Source")) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() for citation_handle in citation_list: citation = self.database.get_citation_from_handle(citation_handle) self.doc.start_row() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(citation.get_gramps_id()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') self.doc.write_text(citation.get_page()) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') date = self._get_date(citation.get_date_object()) if date: self.doc.write_text(date) self.doc.end_paragraph() self.doc.end_cell() self.doc.start_cell('TR-TableCell') self.doc.start_paragraph('TR-Normal') source_handle = citation.get_reference_handle() source = self.database.get_source_from_handle(source_handle) self.doc.write_text(source.get_title()) self.doc.end_paragraph() self.doc.end_cell() self.doc.end_row() self.doc.end_table()
class DetDescendantReport(Report): """ Detailed Descendant Report """ def __init__(self, database, options, user): """ Create the DetDescendantReport object that produces the report. The arguments are: database - the Gramps database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. gen - Maximum number of generations to include. inc_id - Whether to include Gramps IDs pagebgg - Whether to include page breaks between generations. pageben - Whether to include page break before End Notes. fulldates - Whether to use full dates instead of just year. listc - Whether to list children. list_children_spouses - Whether to list the spouses of the children incnotes - Whether to include notes. usecall - Whether to use the call name as the first name. repplace - Whether to replace missing Places with ___________. repdate - Whether to replace missing Dates with ___________. computeage - Whether to compute age. verbose - Whether to use complete sentences. numbering - The descendancy numbering system to be utilized. desref - Whether to add descendant references in child list. incphotos - Whether to include images. incnames - Whether to include other names. incevents - Whether to include events. incaddresses - Whether to include addresses. incsrcnotes - Whether to include source notes in the Endnotes section. Only works if Include sources is selected. incmates - Whether to include information about spouses incattrs - Whether to include attributes incpaths - Whether to include the path of descendancy from the start-person to each descendant. incssign - Whether to include a sign ('+') before the descendant number in the child-list to indicate a child has succession. pid - The Gramps ID of the center person for the report. name_format - Preferred format to display names incmateref - Whether to print mate information or reference incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death structure - How to structure the report """ Report.__init__(self, database, options, user) self.map = {} self._user = user menu = options.menu get_option_by_name = menu.get_option_by_name get_value = lambda name: get_option_by_name(name).get_value() self.set_locale(get_value('trans')) stdoptions.run_date_format_option(self, menu) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, self._locale) self.database = CacheProxyDb(self.database) self._db = self.database self.max_generations = get_value('gen') self.pgbrk = get_value('pagebbg') self.pgbrkenotes = get_value('pageben') self.fulldate = get_value('fulldates') use_fulldate = self.fulldate self.listchildren = get_value('listc') self.list_children_spouses = get_value('listc_spouses') self.inc_notes = get_value('incnotes') use_call = get_value('usecall') blankplace = get_value('repplace') blankdate = get_value('repdate') self.calcageflag = get_value('computeage') self.verbose = get_value('verbose') self.numbering = get_value('numbering') self.childref = get_value('desref') self.addimages = get_value('incphotos') self.structure = get_value('structure') self.inc_names = get_value('incnames') self.inc_events = get_value('incevents') self.inc_addr = get_value('incaddresses') self.inc_sources = get_value('incsources') self.inc_srcnotes = get_value('incsrcnotes') self.inc_mates = get_value('incmates') self.inc_attrs = get_value('incattrs') self.inc_paths = get_value('incpaths') self.inc_ssign = get_value('incssign') self.inc_materef = get_value('incmateref') self.want_ids = get_value('inc_id') pid = get_value('pid') self.center_person = self._db.get_person_from_gramps_id(pid) if self.center_person is None: raise ReportError(_("Person %s is not in the Database") % pid) self.gen_handles = {} self.prev_gen_handles = {} self.gen_keys = [] self.dnumber = {} self.dmates = {} self.numbers_printed = list() if blankdate: empty_date = EMPTY_ENTRY else: empty_date = "" if blankplace: empty_place = EMPTY_ENTRY else: empty_place = "" stdoptions.run_name_format_option(self, menu) self.__narrator = Narrator(self._db, self.verbose, use_call, use_fulldate, empty_date, empty_place, nlocale=self._locale, get_endnote_numbers=self.endnotes) self.bibli = Bibliography(Bibliography.MODE_DATE|Bibliography.MODE_PAGE) def apply_henry_filter(self, person_handle, index, pid, cur_gen=1): """ Filter for Henry numbering """ if (not person_handle) or (cur_gen > self.max_generations): return if person_handle in self.dnumber: if self.dnumber[person_handle] > pid: self.dnumber[person_handle] = pid else: self.dnumber[person_handle] = pid self.map[index] = person_handle if len(self.gen_keys) < cur_gen: self.gen_keys.append([index]) else: self.gen_keys[cur_gen-1].append(index) person = self._db.get_person_from_handle(person_handle) index = 0 for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) for child_ref in family.get_child_ref_list(): _ix = max(self.map) self.apply_henry_filter(child_ref.ref, _ix+1, pid+HENRY[index], cur_gen+1) index += 1 def apply_mhenry_filter(self, person_handle, index, pid, cur_gen=1): """ Filter for Modified Henry numbering """ def mhenry(): """ convenience finction """ return str(index) if index < 10 else "(" + str(index) + ")" if (not person_handle) or (cur_gen > self.max_generations): return self.dnumber[person_handle] = pid self.map[index] = person_handle if len(self.gen_keys) < cur_gen: self.gen_keys.append([index]) else: self.gen_keys[cur_gen-1].append(index) person = self._db.get_person_from_handle(person_handle) index = 1 for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) for child_ref in family.get_child_ref_list(): _ix = max(self.map) self.apply_henry_filter(child_ref.ref, _ix+1, pid+mhenry(), cur_gen+1) index += 1 def apply_daboville_filter(self, person_handle, index, pid, cur_gen=1): """ Filter for d'Aboville numbering """ if (not person_handle) or (cur_gen > self.max_generations): return self.dnumber[person_handle] = pid self.map[index] = person_handle if len(self.gen_keys) < cur_gen: self.gen_keys.append([index]) else: self.gen_keys[cur_gen-1].append(index) person = self._db.get_person_from_handle(person_handle) index = 1 for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) for child_ref in family.get_child_ref_list(): _ix = max(self.map) self.apply_daboville_filter(child_ref.ref, _ix+1, pid+"."+str(index), cur_gen+1) index += 1 def apply_mod_reg_filter_aux(self, person_handle, index, cur_gen=1): """ Filter for Record-style (Modified Register) numbering """ if (not person_handle) or (cur_gen > self.max_generations): return self.map[index] = person_handle if len(self.gen_keys) < cur_gen: self.gen_keys.append([index]) else: self.gen_keys[cur_gen-1].append(index) person = self._db.get_person_from_handle(person_handle) for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) for child_ref in family.get_child_ref_list(): _ix = max(self.map) self.apply_mod_reg_filter_aux(child_ref.ref, _ix+1, cur_gen+1) def apply_mod_reg_filter(self, person_handle): """ Entry Filter for Record-style (Modified Register) numbering """ self.apply_mod_reg_filter_aux(person_handle, 1, 1) mod_reg_number = 1 for generation in range(len(self.gen_keys)): for key in self.gen_keys[generation]: person_handle = self.map[key] if person_handle not in self.dnumber: self.dnumber[person_handle] = mod_reg_number mod_reg_number += 1 def write_report(self): """ This function is called by the report system and writes the report. """ if self.numbering == "Henry": self.apply_henry_filter(self.center_person.get_handle(), 1, "1") elif self.numbering == "Modified Henry": self.apply_mhenry_filter(self.center_person.get_handle(), 1, "1") elif self.numbering == "d'Aboville": self.apply_daboville_filter(self.center_person.get_handle(), 1, "1") elif self.numbering == "Record (Modified Register)": self.apply_mod_reg_filter(self.center_person.get_handle()) else: raise AttributeError("no such numbering: '%s'" % self.numbering) name = self._name_display.display_name( self.center_person.get_primary_name()) if not name: name = self._("Unknown") self.doc.start_paragraph("DDR-Title") # feature request 2356: avoid genitive form title = self._("Descendant Report for %(person_name)s" ) % {'person_name' : name} mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() self.numbers_printed = list() if self.structure == "by generation": for generation in range(len(self.gen_keys)): if self.pgbrk and generation > 0: self.doc.page_break() self.doc.start_paragraph("DDR-Generation") text = self._("Generation %d") % (generation+1) mark = IndexMark(text, INDEX_TYPE_TOC, 2) self.doc.write_text(text, mark) self.doc.end_paragraph() if self.childref: self.prev_gen_handles = self.gen_handles.copy() self.gen_handles.clear() for key in self.gen_keys[generation]: person_handle = self.map[key] self.gen_handles[person_handle] = key self.write_person(key) elif self.structure == "by lineage": for key in sorted(self.map): self.write_person(key) else: raise AttributeError("no such structure: '%s'" % self.structure) if self.inc_sources: if self.pgbrkenotes: self.doc.page_break() # it ignores language set for Note type (use locale) endnotes.write_endnotes(self.bibli, self._db, self.doc, printnotes=self.inc_srcnotes, elocale=self._locale) def write_path(self, person): """ determine the path of the person """ path = [] while True: #person changes in the loop family_handle = person.get_main_parents_family_handle() if family_handle: family = self._db.get_family_from_handle(family_handle) mother_handle = family.get_mother_handle() father_handle = family.get_father_handle() if mother_handle and mother_handle in self.dnumber: person = self._db.get_person_from_handle(mother_handle) person_name = self._name_display.display_name( person.get_primary_name()) path.append(person_name) elif father_handle and father_handle in self.dnumber: person = self._db.get_person_from_handle(father_handle) person_name = self._name_display.display_name( person.get_primary_name()) path.append(person_name) else: break else: break index = len(path) if index: self.doc.write_text("(") for name in path: if index == 1: self.doc.write_text(name + "-" + str(index) + ") ") else: # translators: needed for Arabic, ignore otherwise self.doc.write_text(name + "-" + str(index) + self._("; ")) index -= 1 def write_person(self, key): """Output birth, death, parentage, marriage and notes information """ person_handle = self.map[key] person = self._db.get_person_from_handle(person_handle) val = self.dnumber[person_handle] if val in self.numbers_printed: return else: self.numbers_printed.append(val) self.doc.start_paragraph("DDR-First-Entry", "%s." % val) name = self._name_display.display(person) if not name: name = self._("Unknown") mark = utils.get_person_mark(self._db, person) self.doc.start_bold() self.doc.write_text(name, mark) if name[-1:] == '.': self.doc.write_text_citation("%s " % self.endnotes(person)) elif name: self.doc.write_text_citation("%s. " % self.endnotes(person)) self.doc.end_bold() if self.want_ids: self.doc.write_text('(%s)' % person.get_gramps_id()) if self.inc_paths: self.write_path(person) self.doc.end_paragraph() self.write_person_info(person) if (self.inc_mates or self.listchildren or self.inc_notes or self.inc_events or self.inc_attrs): for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) if self.inc_mates: self.__write_mate(person, family) if self.listchildren: self.__write_children(family) if self.inc_notes: self.__write_family_notes(family) first = True if self.inc_events: first = self.__write_family_events(family) if self.inc_attrs: self.__write_family_attrs(family, first) def write_event(self, event_ref): """ write out the details of an event """ text = "" event = self._db.get_event_from_handle(event_ref.ref) if self.fulldate: date = self._get_date(event.get_date_object()) else: date = event.get_date_object().get_year() place = _pd.display_event(self._db, event) self.doc.start_paragraph('DDR-MoreDetails') event_name = self._get_type(event.get_type()) if date and place: # translators: needed for Arabic, ignore otherwise text += self._('%(str1)s, %(str2)s' ) % {'str1' : date, 'str2' : place} elif date: text += '%s' % date elif place: text += '%s' % self._(place) if event.get_description(): if text: text += ". " text += event.get_description() text += self.endnotes(event) if text: text += ". " # translators: needed for French, ignore otherwise text = self._('%(str1)s: %(str2)s' ) % {'str1' : self._(event_name), 'str2' : text} self.doc.write_text_citation(text) if self.inc_attrs: text = "" attr_list = event.get_attribute_list() attr_list.extend(event_ref.get_attribute_list()) for attr in attr_list: if text: # translators: needed for Arabic, ignore otherwise text += self._("; ") attr_name = attr.get_type().type2base() # translators: needed for French, ignore otherwise text += self._("%(type)s: %(value)s%(endnotes)s" ) % {'type' : self._(attr_name), 'value' : attr.get_value(), 'endnotes' : self.endnotes(attr)} text = " " + text self.doc.write_text_citation(text) self.doc.end_paragraph() if self.inc_notes: # if the event or event reference has a note attached to it, # get the text and format it correctly notelist = event.get_note_list() notelist.extend(event_ref.get_note_list()) for notehandle in notelist: note = self._db.get_note_from_handle(notehandle) self.doc.write_styled_note( note.get_styledtext(), note.get_format(), "DDR-MoreDetails", contains_html=(note.get_type() == NoteType.HTML_CODE)) def __write_parents(self, person): """ write out the main parents of a person """ family_handle = person.get_main_parents_family_handle() if family_handle: family = self._db.get_family_from_handle(family_handle) mother_handle = family.get_mother_handle() father_handle = family.get_father_handle() if mother_handle: mother = self._db.get_person_from_handle(mother_handle) mother_name = self._name_display.display_name( mother.get_primary_name()) mother_mark = utils.get_person_mark(self._db, mother) else: mother_name = "" mother_mark = "" if father_handle: father = self._db.get_person_from_handle(father_handle) father_name = self._name_display.display_name( father.get_primary_name()) father_mark = utils.get_person_mark(self._db, father) else: father_name = "" father_mark = "" text = self.__narrator.get_child_string(father_name, mother_name) if text: self.doc.write_text(text) if father_mark: self.doc.write_text("", father_mark) if mother_mark: self.doc.write_text("", mother_mark) def write_marriage(self, person): """ Output marriage sentence. """ is_first = True for family_handle in person.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) spouse_handle = utils.find_spouse(person, family) if spouse_handle: spouse = self._db.get_person_from_handle(spouse_handle) spouse_mark = utils.get_person_mark(self._db, spouse) else: spouse_mark = None text = self.__narrator.get_married_string(family, is_first, self._name_display) if text: self.doc.write_text_citation(text, spouse_mark) if self.want_ids: self.doc.write_text('(%s)' % family.get_gramps_id()) is_first = False def __write_mate(self, person, family): """ Write information about the person's spouse/mate. """ if person.get_gender() == Person.MALE: mate_handle = family.get_mother_handle() else: mate_handle = family.get_father_handle() if mate_handle: mate = self._db.get_person_from_handle(mate_handle) self.doc.start_paragraph("DDR-MoreHeader") name = self._name_display.display(mate) if not name: name = self._("Unknown") mark = utils.get_person_mark(self._db, mate) if family.get_relationship() == FamilyRelType.MARRIED: self.doc.write_text(self._("Spouse: %s" ) % name, mark) else: self.doc.write_text(self._("Relationship with: %s" ) % name, mark) if name[-1:] != '.': self.doc.write_text(".") self.doc.write_text_citation(self.endnotes(mate)) if self.want_ids: self.doc.write_text(' (%s)' % mate.get_gramps_id()) self.doc.end_paragraph() if not self.inc_materef: # Don't want to just print reference self.write_person_info(mate) else: # Check to see if we've married a cousin if mate_handle in self.dnumber: self.doc.start_paragraph('DDR-MoreDetails') self.doc.write_text_citation( self._("Ref: %(number)s. %(name)s" ) % {'number' : self.dnumber[mate_handle], 'name' : name}) self.doc.end_paragraph() else: self.dmates[mate_handle] = person.get_handle() self.write_person_info(mate) def __get_mate_names(self, family): """ get the names of the parents in a family """ mother_handle = family.get_mother_handle() if mother_handle: mother = self._db.get_person_from_handle(mother_handle) mother_name = self._name_display.display(mother) if not mother_name: mother_name = self._("Unknown") else: mother_name = self._("Unknown") father_handle = family.get_father_handle() if father_handle: father = self._db.get_person_from_handle(father_handle) father_name = self._name_display.display(father) if not father_name: father_name = self._("Unknown") else: father_name = self._("Unknown") return mother_name, father_name def __write_children(self, family): """ List the children for the given family. :param family: Family :return: """ if not family.get_child_ref_list(): return mother_name, father_name = self.__get_mate_names(family) self.doc.start_paragraph("DDR-ChildTitle") self.doc.write_text( self._("Children of %(mother_name)s and %(father_name)s" ) % {'father_name' : father_name, 'mother_name' : mother_name}) self.doc.end_paragraph() cnt = 1 for child_ref in family.get_child_ref_list(): child_handle = child_ref.ref child = self._db.get_person_from_handle(child_handle) child_name = self._name_display.display(child) if not child_name: child_name = self._("Unknown") child_mark = utils.get_person_mark(self._db, child) if self.childref and self.prev_gen_handles.get(child_handle): value = str(self.prev_gen_handles.get(child_handle)) child_name += " [%s]" % value if self.inc_ssign: prefix = " " for family_handle in child.get_family_handle_list(): family = self._db.get_family_from_handle(family_handle) if family.get_child_ref_list(): prefix = "+ " break else: prefix = "" if child_handle in self.dnumber: self.doc.start_paragraph("DDR-ChildList", prefix + str(self.dnumber[child_handle]) + " " + utils.roman(cnt).lower() + ".") else: self.doc.start_paragraph("DDR-ChildList", prefix + utils.roman(cnt).lower() + ".") cnt += 1 self.doc.write_text("%s. " % child_name, child_mark) if self.want_ids: self.doc.write_text('(%s) ' % child.get_gramps_id()) self.__narrator.set_subject(child) self.doc.write_text_citation( self.__narrator.get_born_string() or self.__narrator.get_christened_string() or self.__narrator.get_baptised_string()) self.doc.write_text_citation( self.__narrator.get_died_string() or self.__narrator.get_buried_string()) # if the list_children_spouses option is selected: if self.list_children_spouses: # get the family of the child that contains the spouse # of the child. There may be more than one spouse for each # child family_handle_list = child.get_family_handle_list() # for the first spouse, this is true. # For subsequent spouses, make it false is_first_family = True for family_handle in family_handle_list: child_family = self.database.get_family_from_handle( family_handle ) self.doc.write_text_citation( self.__narrator.get_married_string( child_family, is_first_family, self._name_display ) ) is_first_family = False self.doc.end_paragraph() def __write_family_notes(self, family): """ Write the notes for the given family. """ notelist = family.get_note_list() if len(notelist) > 0: mother_name, father_name = self.__get_mate_names(family) self.doc.start_paragraph("DDR-NoteHeader") self.doc.write_text( self._('Notes for %(mother_name)s and %(father_name)s:' ) % {'mother_name' : mother_name, 'father_name' : father_name}) self.doc.end_paragraph() for notehandle in notelist: note = self._db.get_note_from_handle(notehandle) self.doc.write_styled_note(note.get_styledtext(), note.get_format(), "DDR-Entry") def __write_family_events(self, family): """ List the events for the given family. """ if not family.get_event_ref_list(): return mother_name, father_name = self.__get_mate_names(family) first = True for event_ref in family.get_event_ref_list(): if first: self.doc.start_paragraph('DDR-MoreHeader') self.doc.write_text( self._('More about %(mother_name)s and %(father_name)s:' ) % {'mother_name' : mother_name, 'father_name' : father_name}) self.doc.end_paragraph() first = False self.write_event(event_ref) return first def __write_family_attrs(self, family, first): """ List the attributes for the given family. """ attrs = family.get_attribute_list() if first and attrs: mother_name, father_name = self.__get_mate_names(family) self.doc.start_paragraph('DDR-MoreHeader') self.doc.write_text( self._('More about %(mother_name)s and %(father_name)s:' ) % {'mother_name' : mother_name, 'father_name' : father_name}) self.doc.end_paragraph() for attr in attrs: self.doc.start_paragraph('DDR-MoreDetails') attr_name = self._get_type(attr.get_type()) text = self._("%(type)s: %(value)s%(endnotes)s" ) % {'type' : self._(attr_name), 'value' : attr.get_value(), 'endnotes' : self.endnotes(attr)} self.doc.write_text_citation(text) self.doc.end_paragraph() if self.inc_notes: # if the attr or attr reference has a note attached to it, # get the text and format it correctly notelist = attr.get_note_list() for notehandle in notelist: note = self._db.get_note_from_handle(notehandle) self.doc.write_styled_note(note.get_styledtext(), note.get_format(), "DDR-MoreDetails") def write_person_info(self, person): """ write out all the person's information """ name = self._name_display.display(person) if not name: name = self._("Unknown") self.__narrator.set_subject(person) plist = person.get_media_list() if self.addimages and len(plist) > 0: photo = plist[0] utils.insert_image(self._db, self.doc, photo, self._user) self.doc.start_paragraph("DDR-Entry") if not self.verbose: self.__write_parents(person) text = self.__narrator.get_born_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_baptised_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_christened_string() if text: self.doc.write_text_citation(text) text = self.__narrator.get_died_string(self.calcageflag) if text: self.doc.write_text_citation(text) text = self.__narrator.get_buried_string() if text: self.doc.write_text_citation(text) if self.verbose: self.__write_parents(person) self.write_marriage(person) self.doc.end_paragraph() notelist = person.get_note_list() if len(notelist) > 0 and self.inc_notes: self.doc.start_paragraph("DDR-NoteHeader") # feature request 2356: avoid genitive form self.doc.write_text(self._("Notes for %s") % name) self.doc.end_paragraph() for notehandle in notelist: note = self._db.get_note_from_handle(notehandle) self.doc.write_styled_note( note.get_styledtext(), note.get_format(), "DDR-Entry", contains_html=(note.get_type() == NoteType.HTML_CODE)) first = True if self.inc_names: for alt_name in person.get_alternate_names(): if first: self.doc.start_paragraph('DDR-MoreHeader') self.doc.write_text(self._('More about %(person_name)s:' ) % {'person_name' : name}) self.doc.end_paragraph() first = False self.doc.start_paragraph('DDR-MoreDetails') atype = self._get_type(alt_name.get_type()) aname = alt_name.get_regular_name() self.doc.write_text_citation( self._('%(type)s: %(value)s%(endnotes)s' ) % {'type' : self._(atype), 'value' : aname, 'endnotes' : self.endnotes(alt_name)}) self.doc.end_paragraph() if self.inc_events: for event_ref in person.get_primary_event_ref_list(): if first: self.doc.start_paragraph('DDR-MoreHeader') self.doc.write_text(self._('More about %(person_name)s:' ) % {'person_name' : name}) self.doc.end_paragraph() first = 0 self.write_event(event_ref) if self.inc_addr: for addr in person.get_address_list(): if first: self.doc.start_paragraph('DDR-MoreHeader') self.doc.write_text(self._('More about %(person_name)s:' ) % {'person_name' : name}) self.doc.end_paragraph() first = False self.doc.start_paragraph('DDR-MoreDetails') text = utils.get_address_str(addr) if self.fulldate: date = self._get_date(addr.get_date_object()) else: date = addr.get_date_object().get_year() self.doc.write_text(self._('Address: ')) if date: # translators: needed for Arabic, ignore otherwise self.doc.write_text(self._('%s, ') % date) self.doc.write_text(text) self.doc.write_text_citation(self.endnotes(addr)) self.doc.end_paragraph() if self.inc_attrs: attrs = person.get_attribute_list() if first and attrs: self.doc.start_paragraph('DDR-MoreHeader') self.doc.write_text( self._('More about %(person_name)s:' ) % {'person_name' : name}) self.doc.end_paragraph() first = False for attr in attrs: self.doc.start_paragraph('DDR-MoreDetails') attr_name = attr.get_type().type2base() # translators: needed for French, ignore otherwise text = self._("%(type)s: %(value)s%(endnotes)s" ) % {'type' : self._(attr_name), 'value' : attr.get_value(), 'endnotes' : self.endnotes(attr)} self.doc.write_text_citation(text) self.doc.end_paragraph() def endnotes(self, obj): """ write out any endnotes/footnotes """ if not obj or not self.inc_sources: return "" txt = endnotes.cite_source(self.bibli, self._db, obj, self._locale) if txt: txt = '<super>' + txt + '</super>' return txt
class AncestorReport(Report): """ Ancestor Report class """ def __init__(self, database, options, user): """ Create the AncestorReport object that produces the Ahnentafel report. The arguments are: database - the GRAMPS database instance options - instance of the Options class for this report user - a gen.user.User() instance This report needs the following parameters (class variables) that come in the options class. gen - Maximum number of generations to include. pagebbg - Whether to include page breaks between generations. name_format - Preferred format to display names incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) self.map = {} menu = options.menu lang = menu.get_option_by_name('trans').get_value() rlocale = self.set_locale(lang) stdoptions.run_private_data_option(self, menu) stdoptions.run_living_people_option(self, menu, rlocale) self.database = CacheProxyDb(self.database) self.max_generations = menu.get_option_by_name('maxgen').get_value() self.pgbrk = menu.get_option_by_name('pagebbg').get_value() self.opt_namebrk = menu.get_option_by_name('namebrk').get_value() pid = menu.get_option_by_name('pid').get_value() self.center_person = self.database.get_person_from_gramps_id(pid) if (self.center_person == None) : raise ReportError(_("Person %s is not in the Database") % pid ) stdoptions.run_name_format_option(self, menu) self.__narrator = Narrator(self.database, use_fulldate=True, nlocale=rlocale) def apply_filter(self, person_handle, index, generation=1): """ Recursive function to walk back all parents of the current person. When max_generations are hit, we stop the traversal. """ # check for end of the current recursion level. This happens # if the person handle is None, or if the max_generations is hit if not person_handle or generation > self.max_generations: return # store the person in the map based off their index number # which is passed to the routine. self.map[index] = person_handle # retrieve the Person instance from the database from the # passed person_handle and find the parents from the list. # Since this report is for natural parents (birth parents), # we have to handle that parents may not person = self.database.get_person_from_handle(person_handle) if person is None: return father_handle = None mother_handle = None for family_handle in person.get_parent_family_handle_list(): family = self.database.get_family_from_handle(family_handle) # filter the child_ref_list to find the reference that matches # the passed person. There should be exactly one, but there is # nothing that prevents the same child in the list multiple times. ref = [ c for c in family.get_child_ref_list() if c.get_reference_handle() == person_handle] if ref: # If the father_handle is not defined and the relationship is # BIRTH, then we have found the birth father. Same applies to # the birth mother. If for some reason, the we have multiple # people defined as the birth parents, we will select based on # priority in the list if not father_handle and \ ref[0].get_father_relation() == ChildRefType.BIRTH: father_handle = family.get_father_handle() if not mother_handle and \ ref[0].get_mother_relation() == ChildRefType.BIRTH: mother_handle = family.get_mother_handle() # Recursively call the function. It is okay if the handle is None, # since routine handles a handle of None self.apply_filter(father_handle, index*2, generation+1) self.apply_filter(mother_handle, (index*2)+1, generation+1) def write_report(self): """ The routine the actually creates the report. At this point, the document is opened and ready for writing. """ # Call apply_filter to build the self.map array of people in the # database that match the ancestry. self.apply_filter(self.center_person.get_handle(), 1) # Write the title line. Set in INDEX marker so that this section will be # identified as a major category if this is included in a Book report. name = self._name_display.display_formal(self.center_person) # feature request 2356: avoid genitive form title = self._("Ahnentafel Report for %s") % name mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.start_paragraph("AHN-Title") self.doc.write_text(title, mark) self.doc.end_paragraph() # get the entries out of the map, and sort them. generation = 0 for key in sorted(self.map): # check the index number to see if we need to start a new generation if generation == log2(key): # generate a page break if requested if self.pgbrk and generation > 0: self.doc.page_break() generation += 1 # Create the Generation title, set an index marker gen_text = self._("Generation %d") % generation mark = None # don't need any with no page breaks if self.pgbrk: mark = IndexMark(gen_text, INDEX_TYPE_TOC, 2) self.doc.start_paragraph("AHN-Generation") self.doc.write_text(gen_text, mark) self.doc.end_paragraph() # Build the entry self.doc.start_paragraph("AHN-Entry","%d." % key) person = self.database.get_person_from_handle(self.map[key]) if person is None: continue name = self._name_display.display(person) mark = utils.get_person_mark(self.database, person) # write the name in bold self.doc.start_bold() self.doc.write_text(name.strip(), mark) self.doc.end_bold() # terminate with a period if it is not already terminated. # This can happen if the person's name ends with something 'Jr.' if name[-1:] == '.': self.doc.write_text(" ") else: self.doc.write_text(". ") # Add a line break if requested (not implemented yet) if self.opt_namebrk: self.doc.write_text('\n') self.__narrator.set_subject(person) self.doc.write_text(self.__narrator.get_born_string()) self.doc.write_text(self.__narrator.get_baptised_string()) self.doc.write_text(self.__narrator.get_christened_string()) self.doc.write_text(self.__narrator.get_died_string()) self.doc.write_text(self.__narrator.get_buried_string()) self.doc.end_paragraph()
class RecordsReport(Report): """ Records Report """ def __init__(self, database, options, user): """ This report needs the following parameters (class variables) that come in the options class. incl_private - Whether to include private data living_people - How to handle living people years_past_death - Consider as living this many years after death """ Report.__init__(self, database, options, user) menu = options.menu lang = options.menu.get_option_by_name('trans').get_value() self._locale = self.set_locale(lang) stdoptions.run_private_data_option(self, menu) living_opt = stdoptions.run_living_people_option(self, menu, self._locale) self.database = CacheProxyDb(self.database) self._lv = menu.get_option_by_name('living_people').get_value() for (value, description) in living_opt.get_items(xml_items=True): if value == self._lv: living_desc = self._(description) break self.living_desc = self._( "(Living people: %(option_name)s)") % {'option_name': living_desc} filter_option = menu.get_option_by_name('filter') self.filter = filter_option.get_filter() self.top_size = menu.get_option_by_name('top_size').get_value() self.callname = menu.get_option_by_name('callname').get_value() self.footer = menu.get_option_by_name('footer').get_value() self.include = {} for (text, varname, default) in RECORDS: self.include[varname] = menu.get_option_by_name(varname).get_value() self._nf = stdoptions.run_name_format_option(self, menu) def write_report(self): """ Build the actual report. """ records = find_records(self.database, self.filter, self.top_size, self.callname, trans_text=self._, name_format=self._nf, living_mode=self._lv) self.doc.start_paragraph('REC-Title') title = self._("Records") mark = IndexMark(title, INDEX_TYPE_TOC, 1) self.doc.write_text(title, mark) self.doc.end_paragraph() self.doc.start_paragraph('REC-Subtitle') filter_name = self.filter.get_name(self._locale) self.doc.write_text("(%s)" % filter_name) self.doc.end_paragraph() if self._lv != LivingProxyDb.MODE_INCLUDE_ALL: self.doc.start_paragraph('REC-Subtitle') self.doc.write_text(self.living_desc) self.doc.end_paragraph() for (text, varname, top) in records: if not self.include[varname]: continue self.doc.start_paragraph('REC-Heading') self.doc.write_text(self._(text)) self.doc.end_paragraph() last_value = None rank = 0 for (number, (sort, value, name, handletype, handle)) in enumerate(top): mark = None if handletype == 'Person': person = self.database.get_person_from_handle(handle) mark = utils.get_person_mark(self.database, person) elif handletype == 'Family': family = self.database.get_family_from_handle(handle) # librecords.py checks that the family has both # a father and a mother and also that each one is # in the filter if any filter was used, so we don't # have to do any similar checking here, it's been done f_handle = family.get_father_handle() dad = self.database.get_person_from_handle(f_handle) f_mark = utils.get_person_mark(self.database, dad) m_handle = family.get_mother_handle() mom = self.database.get_person_from_handle(m_handle) m_mark = utils.get_person_mark(self.database, mom) else: raise ReportError(_( "Option '%(opt_name)s' is present " "in %(file)s\n but is not known to " "the module. Ignoring...") % {'opt_name': handletype, 'file': 'libnarrate.py'}) # since the error is very unlikely I reused the string if value != last_value: last_value = value rank = number self.doc.start_paragraph('REC-Normal') self.doc.write_text( self._("%(number)s. ") % {'number': rank+1}) self.doc.write_markup(str(name), name.get_tags(), mark) if handletype == 'Family': self.doc.write_text('', f_mark) self.doc.write_text('', m_mark) if isinstance(value, Span): tvalue = value.get_repr(dlocale=self._locale) else: tvalue = value self.doc.write_text(" (%s)" % tvalue) self.doc.end_paragraph() self.doc.start_paragraph('REC-Footer') self.doc.write_text(self.footer) self.doc.end_paragraph()