def create_family(ptr: str, level: int = 0, husb_ptrs: Tuple[str, ...] = None, wife_ptrs: Tuple[str, ...] = None, child_ptrs: Tuple[str, ...] = None, marriage_place: str = None, marriage_date: str = None, divorce_place: str = None, divorce_date: str = None): family_element = FamilyElement(level, ptr, tags.GEDCOM_TAG_FAMILY, '') if husb_ptrs: for husb_ptr in husb_ptrs: family_element.add_child_element(Element(level + 1, '', tags.GEDCOM_TAG_HUSBAND, husb_ptr)) if wife_ptrs: for wife_ptr in wife_ptrs: family_element.add_child_element(Element(level + 1, '', tags.GEDCOM_TAG_WIFE, wife_ptr)) if child_ptrs: for child_ptr in child_ptrs: family_element.add_child_element(Element(level + 1, '', tags.GEDCOM_TAG_CHILD, child_ptr)) if marriage_date is not None or marriage_place is not None: family_element.add_child_element( create_event(tags.GEDCOM_TAG_MARRIAGE, marriage_place, marriage_date, level + 1)) if divorce_place is not None or divorce_date is not None: family_element.add_child_element(create_event(tags.GEDCOM_TAG_DIVORCE, divorce_place, divorce_date, level + 1)) return family_element
def export_sex(self, character: Character, element: Element): sex = character.get_property(PropertyTag.SEX).value if sex != Sex.UNDEFINED: element.new_child_element('SEX', '', { Sex.MALE: 'M', Sex.FEMALE: 'F' }[sex])
def test_gen_individual(self): birth_date = datetime.datetime(1980, 1, 1) death_date = datetime.datetime(2040, 1, 1) legal_name = LegalName(first_name="David", last_name="Schmidt") legal_name.save() birth_location = Location(city='New York', state='NY', country='US') birth_location.save() death_location = Location(city='Boston', state='MA', country='US') death_location.save() person = Person( legal_name=legal_name, gender="Male", birth_date=birth_date, birth_location=birth_location, death_date=death_date, death_location=death_location, ) person.save() alternate_name = AlternateName(first_name='Lance', last_name='Springer', person=person) alternate_name.save() partner_partnership = Partnership() partner_partnership.save() person_partnership = PersonPartnership(person=person, partnership=partner_partnership) person_partnership.save() child_partnership = Partnership() child_partnership.save() child_partnership.children.add(person) ptr, individual = gedcom_generator.gen_individual(person) expected = gedcom_helpers.create_individual( f"@PERSON_{person.id}@", name="David Schmidt", sex='M', birth_place='New York, NY, US', birth_date=birth_date.strftime("%d %b %Y").upper(), death_place='Boston, MA, US', death_date=death_date.strftime("%d %b %Y").upper()) expected.add_child_element( Element(1, '', tags.GEDCOM_TAG_NAME, 'Lance Springer')) expected.add_child_element( Element(1, '', tags.GEDCOM_TAG_GIVEN_NAME, 'David')) expected.add_child_element( Element(1, '', tags.GEDCOM_TAG_SURNAME, 'Schmidt')) expected.add_child_element( Element(1, '', tags.GEDCOM_TAG_FAMILY_SPOUSE, f'@PARTNERSHIP_{partner_partnership.id}@')) expected.add_child_element( Element(1, '', tags.GEDCOM_TAG_FAMILY_CHILD, f'@PARTNERSHIP_{child_partnership.id}@')) self.assertTrue(gedcom_helpers.element_equals(individual, expected))
def create_character_element(self, character: Character): element = Element(0, '@{}@'.format(character.id), 'INDI', '') element.new_child_element('NAME', '', str(character.label)) for property_tag in self.properties: try: export_gedcom_property_methods[property_tag](self, character, element) except (ExportPropertyException, EntityException): self.logger.error('{}: {} is impossible to export'.format( self.__class__.__name__, property_tag)) self.create_family(character) self.elements[character.id] = element
def _get_date_value(element: Element) -> UncertainDate: for sub_element in element.get_child_elements(): if FamilyTree._has_tag(sub_element, Tag.Date): date, unparsed = Date.parse(sub_element.get_value()) assert (date is not None and unparsed == ""), f"Must parse date, but got {date, unparsed}" return date return None
def create_event(tag_type: str, place: str = None, date: str = None, level: int = 1): event_element = Element(level, '', tag_type, '') if place is not None: event_place_element = Element(level + 1, '', tags.GEDCOM_TAG_PLACE, place) event_element.add_child_element(event_place_element) if date is not None: event_date_element = Element(level + 1, '', tags.GEDCOM_TAG_DATE, date) event_element.add_child_element(event_date_element) return event_element
def element_equals(element1: Element, element2: Element): if not element_values_equals(element1, element2): print(f'{element1.to_gedcom_string()} != {element2.to_gedcom_string()}', file=sys.stderr) return False if len(element1.get_child_elements()) != len(element2.get_child_elements()): print(f'element1.len ({len(element1.get_child_elements())}) != ' f'element2.len ({len(element2.get_child_elements())})', file=sys.stderr) return False child_elements_2 = element2.get_child_elements().copy() for e1 in element1.get_child_elements(): found_match = False for e2 in child_elements_2: if element_values_equals(e1, e2): found_match = True child_elements_2.remove(e2) break if not found_match: print(f'No match found for element: {e1.to_gedcom_string()}', file=sys.stderr) return False return True
def __export_place(place: Place, event: Element): plac_element = event.new_child_element('PLAC', '', place.label) coordinates = place.get_property(PropertyTag.COORDINATE_LOCATION).value if coordinates.latitude and coordinates.longitude: map_element = plac_element.new_child_element('MAP') map_element.new_child_element( 'LATI', '', '{}{}'.format('N' if coordinates.latitude > 0 else 'S', abs(coordinates.latitude))) map_element.new_child_element( 'LONG', '', '{}{}'.format('E' if coordinates.longitude > 0 else 'W', abs(coordinates.longitude)))
def create_individual(ptr: str, level: int = 0, name: str = None, sex: str = None, birth_place: str = None, birth_date: str = None, death_place: str = None, death_date: str = None, family_spouse_ptr: str = None, family_child_ptr: str = None): individual_element = IndividualElement(level, ptr, tags.GEDCOM_TAG_INDIVIDUAL, '') if name is not None: individual_element.add_child_element(Element(level + 1, '', tags.GEDCOM_TAG_NAME, name)) if sex is not None: individual_element.add_child_element(Element(level + 1, '', tags.GEDCOM_TAG_SEX, sex)) if birth_place is not None or birth_date is not None: individual_element.add_child_element(create_event(tags.GEDCOM_TAG_BIRTH, birth_place, birth_date, level + 1)) if death_place is not None or death_date is not None: individual_element.add_child_element(create_event(tags.GEDCOM_TAG_DEATH, death_place, death_date, level + 1)) if family_spouse_ptr is not None: individual_element.add_child_element(Element(level + 1, '', tags.GEDCOM_TAG_FAMILY_SPOUSE, family_spouse_ptr)) if family_child_ptr is not None: individual_element.add_child_element(Element(level + 1, '', tags.GEDCOM_TAG_FAMILY_CHILD, family_child_ptr)) return individual_element
def create_family_element(self, family: Family): element = Element(0, '@{}@'.format(family.family_id), 'FAM', '') if family.father_id and family.father_id in self.elements.keys(): element.new_child_element('HUSB', '', '@{}@'.format(family.father_id)) self.elements[family.father_id].new_child_element( 'FAMS', '', '@{}@'.format(family.family_id)) if family.mother_id and family.mother_id in self.elements.keys(): element.new_child_element('WIFE', '', '@{}@'.format(family.mother_id)) self.elements[family.mother_id].new_child_element( 'FAMS', '', '@{}@'.format(family.family_id)) for child_id in family.children_ids: if child_id in self.database.cache.keys(): element.new_child_element('CHIL', '', '@{}@'.format(child_id)) self.elements[child_id].new_child_element( 'FAMC', '', '@{}@'.format(family.family_id)) self.elements[family.family_id] = element
def test_filter_child_elements(self): individual = gen_test_individual() elements_with_name_tag = gedcom_helpers.filter_child_elements( individual, tag=tags.GEDCOM_TAG_NAME) for element in elements_with_name_tag: self.assertEqual(element.get_tag(), tags.GEDCOM_TAG_NAME) elements_with_name_value = gedcom_helpers.filter_child_elements( individual, value="/Some/ Guy") for element in elements_with_name_value: self.assertEqual(element.get_value(), '/Some/ Guy') # This is not a real case where a pointer would be used in GEDCOM, this is just for testing individual.add_child_element( Element(1, '@P1@', tags.GEDCOM_TAG_INDIVIDUAL, '')) elements_with_family_pointer = gedcom_helpers.filter_child_elements( individual, pointer="@P1@") for element in elements_with_family_pointer: self.assertEqual(element.get_pointer(), '@P1@') elements_with_tag_and_value = gedcom_helpers.filter_child_elements( individual, tag=tags.GEDCOM_TAG_SEX, value='M') for element in elements_with_tag_and_value: self.assertEqual(element.get_tag(), tags.GEDCOM_TAG_SEX) self.assertEqual(element.get_value(), 'M') match_tag_with_multiple_tags = gedcom_helpers \ .filter_child_elements(individual, (tags.GEDCOM_TAG_NAME, tags.GEDCOM_TAG_SEX)) for element in match_tag_with_multiple_tags: self.assertIn(element.get_tag(), (tags.GEDCOM_TAG_NAME, tags.GEDCOM_TAG_SEX)) # tags should not be empty, and we know this is true for the example no_elements = gedcom_helpers.filter_child_elements(individual, tag="") self.assertEqual(no_elements, []) contains_tag = gedcom_helpers.filter_child_elements(individual, tag=True) for element in contains_tag: self.assertNotIn(element.get_tag(), ('', None)) not_contains_value = gedcom_helpers.filter_child_elements(individual, value=False) for element in not_contains_value: self.assertIn(element.get_value(), ('', None))
def create_family_element(self, family: Family): if family.mother_id == 'Q271506': print('Q271506') element = Element(0, '@{}@'.format(family.id), 'FAM', '') if family.father_id and family.father_id in self.elements.keys(): element.new_child_element('HUSB', '', '@{}@'.format(family.father_id)) self.elements[family.father_id].new_child_element( 'FAMS', '', '@{}@'.format(family.id)) if family.mother_id and family.mother_id in self.elements.keys(): element.new_child_element('WIFE', '', '@{}@'.format(family.mother_id)) self.elements[family.mother_id].new_child_element( 'FAMS', '', '@{}@'.format(family.id)) for child_id in family.children_ids: if child_id in self.characters.keys(): element.new_child_element('CHIL', '', '@{}@'.format(child_id)) self.elements[child_id].new_child_element( 'FAMC', '', '@{}@'.format(family.id)) self.elements[family.id] = element
def create_character_element(self, character: Character): element = Element(0, '@{}@'.format(character.id), 'INDI', '') name_element = element.new_child_element('NAME', '', str(character.label)) if character.data.given_name and character.data.family_name: name_element.new_child_element('GIVN', '', character.data.given_name) name_element.new_child_element('SURN', '', character.data.family_name) if character.data.birth_date: birth_element = element.new_child_element('BIRT') birth_element.new_child_element('DATE', '', str(character.data.birth_date)) if character.data.birth_date: death_element = element.new_child_element('DEAT') death_element.new_child_element('DATE', '', str(character.data.birth_date)) if character.sex: element.new_child_element('SEX', '', character.sex) self.create_family(character) self.elements[character.id] = element
def test_element_values_equals(self): element_1 = Element(1, '', '', '') element_2 = Element(2, '', '', '') self.assertFalse( gedcom_helpers.element_values_equals(element_1, element_2)) element_3 = Element(1, '', '', '') self.assertTrue( gedcom_helpers.element_values_equals(element_1, element_3)) self.assertFalse( gedcom_helpers.element_values_equals(element_2, element_3)) element_4 = Element(2, '', '', 'value') self.assertFalse( gedcom_helpers.element_values_equals(element_2, element_4)) element_5 = Element(2, 'pointer', '', '') self.assertFalse( gedcom_helpers.element_values_equals(element_2, element_5)) element_6 = Element(2, '', 'tag', '') self.assertFalse( gedcom_helpers.element_values_equals(element_2, element_6)) element_7 = Element(2, 'pointer', 'tag', 'value') self.assertFalse( gedcom_helpers.element_values_equals(element_2, element_7))
def test_gen_head_and_submitter(self): user = User(username="******") tree = Tree(title="Test", creator=user) head, submitter = gedcom_generator.gen_head_and_submitter(tree) expected_head = Element(0, '', tags.GEDCOM_TAG_HEAD, '') charset_element = Element(1, '', tags.GEDCOM_TAG_CHARSET, tags.GEDCOM_CHARSET_UTF8) expected_head.add_child_element(charset_element) gedcom_element = Element(1, '', tags.GEDCOM_TAG_GEDCOM, '') version_element = Element(2, '', tags.GEDCOM_TAG_VERSION, '5.5') gedcom_element.add_child_element(version_element) format_element = Element(2, '', tags.GEDCOM_TAG_FORM, 'Lineage-Linked') gedcom_element.add_child_element(format_element) expected_head.add_child_element(gedcom_element) submitter_ptr = '@SUBMITTER@' expected_head.add_child_element( Element(1, '', tags.GEDCOM_TAG_SUBMITTER, submitter_ptr)) expected_submitter = Element(0, submitter_ptr, tags.GEDCOM_TAG_SUBMITTER, '') expected_submitter.add_child_element( Element(1, '', tags.GEDCOM_TAG_NAME, tree.creator.username)) self.assertTrue(gedcom_helpers.element_equals(head, expected_head)) self.assertTrue( gedcom_helpers.element_equals(submitter, expected_submitter))
def get_create_child_by_tag(element: Element, tag: str): childs = element.get_child_elements() for child in childs: if child.get_tag() == tag: return child return element.new_child_element(tag)
def __parse_line(line_number, line, last_element, strict=True): """Parse a line from a GEDCOM 5.5 formatted document Each line should have the following (bracketed items optional): level + ' ' + [pointer + ' ' +] tag + [' ' + line_value] :type line_number: int :type line: str :type last_element: Element :type strict: bool :rtype: Element """ # Level must start with non-negative int, no leading zeros. level_regex = '^(0|[1-9]+[0-9]*) ' # Pointer optional, if it exists it must be flanked by `@` pointer_regex = '(@[^@]+@ |)' # Tag must be an alphanumeric string tag_regex = '([A-Za-z0-9_]+)' # Value optional, consists of anything after a space to end of line value_regex = '( [^\n\r]*|)' # End of line defined by `\n` or `\r` end_of_line_regex = '([\r\n]{1,2})' # Complete regex gedcom_line_regex = level_regex + pointer_regex + tag_regex + value_regex + end_of_line_regex regex_match = regex.match(gedcom_line_regex, line) if regex_match is None: if strict: error_message = ( "Line %d of document violates GEDCOM format 5.5" % line_number + "\nSee: https://chronoplexsoftware.com/gedcomvalidator/gedcom/gedcom-5.5.pdf" ) raise GedcomFormatViolationError(error_message) else: # Quirk check - see if this is a line without a CRLF (which could be the last line) last_line_regex = level_regex + pointer_regex + tag_regex + value_regex regex_match = regex.match(last_line_regex, line) if regex_match is not None: line_parts = regex_match.groups() level = int(line_parts[0]) pointer = line_parts[1].rstrip(' ') tag = line_parts[2] value = line_parts[3][1:] crlf = '\n' else: # Quirk check - Sometimes a gedcom has a text field with a CR. # This creates a line without the standard level and pointer. # If this is detected then turn it into a CONC or CONT. line_regex = '([^\n\r]*|)' cont_line_regex = line_regex + end_of_line_regex regex_match = regex.match(cont_line_regex, line) line_parts = regex_match.groups() level = last_element.get_level() tag = last_element.get_tag() pointer = None value = line_parts[0][1:] crlf = line_parts[1] if tag != gedcom.tags.GEDCOM_TAG_CONTINUED and tag != gedcom.tags.GEDCOM_TAG_CONCATENATION: # Increment level and change this line to a CONC level += 1 tag = gedcom.tags.GEDCOM_TAG_CONCATENATION else: line_parts = regex_match.groups() level = int(line_parts[0]) pointer = line_parts[1].rstrip(' ') tag = line_parts[2] value = line_parts[3][1:] crlf = line_parts[4] # Check level: should never be more than one higher than previous line. if level > last_element.get_level() + 1: error_message = ( "Line %d of document violates GEDCOM format 5.5" % line_number + "\nLines must be no more than one level higher than previous line." + "\nSee: https://chronoplexsoftware.com/gedcomvalidator/gedcom/gedcom-5.5.pdf" ) raise GedcomFormatViolationError(error_message) # Create element. Store in list and dict, create children and parents. if tag == gedcom.tags.GEDCOM_TAG_INDIVIDUAL: element = IndividualElement(level, pointer, tag, value, crlf, multi_line=False) elif tag == gedcom.tags.GEDCOM_TAG_FAMILY: element = FamilyElement(level, pointer, tag, value, crlf, multi_line=False) elif tag == gedcom.tags.GEDCOM_TAG_FILE: element = FileElement(level, pointer, tag, value, crlf, multi_line=False) elif tag == gedcom.tags.GEDCOM_TAG_OBJECT: element = ObjectElement(level, pointer, tag, value, crlf, multi_line=False) else: element = Element(level, pointer, tag, value, crlf, multi_line=False) # Start with last element as parent, back up if necessary. parent_element = last_element while parent_element.get_level() > level - 1: parent_element = parent_element.get_parent_element() # Add child to parent & parent to child. parent_element.add_child_element(element) return element
def element_values_equals(element1: Element, element2: Element): return element1.get_level() == element2.get_level() \ and element1.get_pointer() == element2.get_pointer() \ and element1.get_tag() == element2.get_tag() \ and element1.get_value() == element2.get_value()
def print_element(self, file, element: Element, depth=0): gedcom_string = element.to_gedcom_string() file.write(gedcom_string) for child in element.get_child_elements(): self.print_element(file, child, depth=depth + 1)
def _has_tag(element: Element, tag: Tag) -> bool: return element.get_tag() == tag.value
def print_element(file, element: Element, depth=0): file.write(element.to_gedcom_string()) for child in element.get_child_elements(): print_element(file, child, depth=depth+1)
def test_initialization(): element = Element(level=-1, pointer="", tag="", value="") assert isinstance(element, Element)