├── tests ├── __init__.py ├── element │ ├── __init__.py │ ├── test_element.py │ ├── test_root.py │ ├── test_file.py │ ├── test_family.py │ ├── test_object.py │ └── test_individual.py ├── test_parser.py └── files │ └── Musterstammbaum.ged ├── docs ├── CNAME └── gedcom │ ├── helpers.html │ ├── element │ ├── index.html │ ├── root.html │ ├── file.html │ ├── family.html │ └── object.html │ └── index.html ├── logo.png ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── Pipfile ├── .travis.yml ├── MANIFEST.in ├── tox.ini ├── setup.cfg ├── gedcom ├── __init__.py ├── element │ ├── __init__.py │ ├── file.py │ ├── family.py │ ├── root.py │ ├── object.py │ ├── element.py │ └── individual.py ├── helpers.py ├── gedcom.md ├── tags.py └── parser.py ├── setup.py ├── .gitignore ├── CHANGELOG.md ├── README.md ├── Pipfile.lock └── LICENSE.txt /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/element/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | gedcom.nickreynke.dev -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickreynke/python-gedcom/HEAD/logo.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | ko_fi: nickreynke 4 | -------------------------------------------------------------------------------- /tests/element/test_element.py: -------------------------------------------------------------------------------- 1 | from gedcom.element.element import Element 2 | 3 | 4 | def test_initialization(): 5 | element = Element(level=-1, pointer="", tag="", value="") 6 | assert isinstance(element, Element) 7 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | setuptools = "*" 8 | wheel = "*" 9 | twine = "*" 10 | tox = "*" 11 | pdoc3 = "*" 12 | 13 | [packages] 14 | 15 | [requires] 16 | python_version = "3.5" 17 | -------------------------------------------------------------------------------- /tests/element/test_root.py: -------------------------------------------------------------------------------- 1 | from gedcom.element.element import Element 2 | from gedcom.element.root import RootElement 3 | 4 | 5 | def test_initialization(): 6 | root_element = RootElement(level=-1, pointer="", tag="ROOT", value="") 7 | assert isinstance(root_element, Element) 8 | assert isinstance(root_element, RootElement) 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | matrix: 4 | include: 5 | - python: '3.5' 6 | env: TOXENV=py35 7 | - python: '3.6' 8 | env: TOXENV=py36 9 | - python: '3.7' 10 | env: TOXENV=py37 11 | - python: '3.8' 12 | env: TOXENV=py38 13 | 14 | install: pip install tox 15 | 16 | script: tox 17 | 18 | notifications: 19 | email: false 20 | -------------------------------------------------------------------------------- /tests/element/test_file.py: -------------------------------------------------------------------------------- 1 | from gedcom.element.element import Element 2 | from gedcom.element.file import FileElement 3 | import gedcom.tags 4 | 5 | 6 | def test_initialization(): 7 | file_element = FileElement(level=-1, pointer="", tag=gedcom.tags.GEDCOM_TAG_FILE, value="") 8 | assert isinstance(file_element, Element) 9 | assert isinstance(file_element, FileElement) 10 | -------------------------------------------------------------------------------- /tests/element/test_family.py: -------------------------------------------------------------------------------- 1 | from gedcom.element.element import Element 2 | from gedcom.element.family import FamilyElement 3 | import gedcom.tags 4 | 5 | 6 | def test_initialization(): 7 | family_element = FamilyElement(level=-1, pointer="", tag=gedcom.tags.GEDCOM_TAG_FAMILY, value="") 8 | assert isinstance(family_element, Element) 9 | assert isinstance(family_element, FamilyElement) 10 | -------------------------------------------------------------------------------- /tests/element/test_object.py: -------------------------------------------------------------------------------- 1 | from gedcom.element.element import Element 2 | from gedcom.element.object import ObjectElement 3 | import gedcom.tags 4 | 5 | 6 | def test_initialization(): 7 | object_element = ObjectElement(level=-1, pointer="", tag=gedcom.tags.GEDCOM_TAG_OBJECT, value="") 8 | assert isinstance(object_element, Element) 9 | assert isinstance(object_element, ObjectElement) 10 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | exclude MANIFEST.in 2 | 3 | # Include the README and the CHANGELOG 4 | include *.md 5 | 6 | # Include the license file 7 | include LICENSE.txt 8 | 9 | # Include the dependency related files 10 | include Pipfile 11 | include Pipfile.lock 12 | 13 | # Docs 14 | exclude logo.png 15 | exclude docs 16 | recursive-exclude docs * 17 | recursive-exclude gedcom *.md 18 | 19 | # Include GEDCOM test files 20 | include tests/files/*.ged 21 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # - check-manifest 2 | # confirm items checked into vcs are in your sdist 3 | # - python setup.py check 4 | # confirm required package meta-data in setup.py 5 | 6 | [tox] 7 | envlist = py{35,36,37,38} 8 | 9 | [testenv] 10 | basepython = 11 | py35: python3.5 12 | py36: python3.6 13 | py37: python3.7 14 | py38: python3.8 15 | deps = 16 | check-manifest 17 | flake8 18 | pytest 19 | commands = 20 | check-manifest --ignore tox.ini,tests* 21 | python setup.py check -m -s 22 | flake8 . 23 | py.test tests 24 | [flake8] 25 | exclude = .tox,*.egg,build,data 26 | select = E,W,F 27 | ignore = E501,W503 28 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | # This includes the license file(s) in the wheel. 3 | # https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file 4 | license_files = LICENSE.txt 5 | 6 | [bdist_wheel] 7 | # This flag says to generate wheels that support both Python 2 and Python 8 | # 3. If your code will not run unchanged on both Python 2 and 3, you will 9 | # need to generate separate wheels for each Python version that you 10 | # support. Removing this line (or setting universal to 0) will prevent 11 | # bdist_wheel from trying to make a universal wheel. For more see: 12 | # https://packaging.python.org/guides/distributing-packages-using-setuptools/#wheels 13 | universal=1 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature request] " 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[Bug] " 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: (Example) 15 | 1. Import '...' 16 | 2. Use '....' 17 | 3. Make changes to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Version** 27 | Version of the Python GEDCOM module you are using. 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /tests/element/test_individual.py: -------------------------------------------------------------------------------- 1 | from gedcom.element.element import Element 2 | from gedcom.element.individual import IndividualElement 3 | import gedcom.tags 4 | 5 | 6 | def test_initialization(): 7 | individual_element = IndividualElement(level=-1, pointer="", tag=gedcom.tags.GEDCOM_TAG_INDIVIDUAL, value="") 8 | assert isinstance(individual_element, Element) 9 | assert isinstance(individual_element, IndividualElement) 10 | 11 | 12 | def test_get_all_names(): 13 | element = IndividualElement(level=0, pointer="@I5@", tag="INDI", value="") 14 | element.new_child_element(tag="NAME", value="First /Last/") 15 | element.new_child_element(tag="SEX", value="M") 16 | birth = element.new_child_element(tag="BIRT", value="") 17 | birth.new_child_element(tag="DATE", value="1 JAN 1900") 18 | element.new_child_element(tag="NAME", value="Second /Surname/") 19 | 20 | all_names = element.get_all_names() 21 | assert len(all_names) == 2 22 | -------------------------------------------------------------------------------- /gedcom/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Python GEDCOM Parser 4 | # 5 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com) 6 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com) 7 | # Copyright (C) 2016 Andreas Oberritter 8 | # Copyright (C) 2012 Madeleine Price Ball 9 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu) 10 | # Copyright (C) 2005 Brigham Young University 11 | # 12 | # This program is free software; you can redistribute it and/or modify 13 | # it under the terms of the GNU General Public License as published by 14 | # the Free Software Foundation; either version 2 of the License, or 15 | # (at your option) any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU General Public License along 23 | # with this program; if not, write to the Free Software Foundation, Inc., 24 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 25 | # 26 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html 27 | 28 | """ 29 | A Python module for parsing, analyzing, and manipulating GEDCOM files. 30 | 31 | .. include:: ./gedcom.md 32 | """ 33 | 34 | __all__ = [ 35 | # Subpackages 36 | "element", 37 | # Modules 38 | "helpers", 39 | "parser", 40 | "tags" 41 | ] 42 | -------------------------------------------------------------------------------- /gedcom/element/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Python GEDCOM Parser 4 | # 5 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com) 6 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com) 7 | # Copyright (C) 2016 Andreas Oberritter 8 | # Copyright (C) 2012 Madeleine Price Ball 9 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu) 10 | # Copyright (C) 2005 Brigham Young University 11 | # 12 | # This program is free software; you can redistribute it and/or modify 13 | # it under the terms of the GNU General Public License as published by 14 | # the Free Software Foundation; either version 2 of the License, or 15 | # (at your option) any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU General Public License along 23 | # with this program; if not, write to the Free Software Foundation, Inc., 24 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 25 | # 26 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html 27 | 28 | """ 29 | Module containing all relevant elements generated by a `gedcom.parser.Parser`. 30 | An element represents a line within GEDCOM data. 31 | """ 32 | 33 | __all__ = [ 34 | "element", 35 | "family", 36 | "file", 37 | "individual", 38 | "object", 39 | "root" 40 | ] 41 | -------------------------------------------------------------------------------- /gedcom/element/file.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Python GEDCOM Parser 4 | # 5 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com) 6 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com) 7 | # Copyright (C) 2016 Andreas Oberritter 8 | # Copyright (C) 2012 Madeleine Price Ball 9 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu) 10 | # Copyright (C) 2005 Brigham Young University 11 | # 12 | # This program is free software; you can redistribute it and/or modify 13 | # it under the terms of the GNU General Public License as published by 14 | # the Free Software Foundation; either version 2 of the License, or 15 | # (at your option) any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU General Public License along 23 | # with this program; if not, write to the Free Software Foundation, Inc., 24 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 25 | # 26 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html 27 | 28 | """GEDCOM element consisting of tag `gedcom.tags.GEDCOM_TAG_FILE`""" 29 | 30 | from gedcom.element.element import Element 31 | import gedcom.tags 32 | 33 | 34 | class NotAnActualFileError(Exception): 35 | pass 36 | 37 | 38 | class FileElement(Element): 39 | 40 | def get_tag(self): 41 | return gedcom.tags.GEDCOM_TAG_FILE 42 | -------------------------------------------------------------------------------- /gedcom/element/family.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Python GEDCOM Parser 4 | # 5 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com) 6 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com) 7 | # Copyright (C) 2016 Andreas Oberritter 8 | # Copyright (C) 2012 Madeleine Price Ball 9 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu) 10 | # Copyright (C) 2005 Brigham Young University 11 | # 12 | # This program is free software; you can redistribute it and/or modify 13 | # it under the terms of the GNU General Public License as published by 14 | # the Free Software Foundation; either version 2 of the License, or 15 | # (at your option) any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU General Public License along 23 | # with this program; if not, write to the Free Software Foundation, Inc., 24 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 25 | # 26 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html 27 | 28 | """GEDCOM element consisting of tag `gedcom.tags.GEDCOM_TAG_FAMILY`""" 29 | 30 | from gedcom.element.element import Element 31 | import gedcom.tags 32 | 33 | 34 | class NotAnActualFamilyError(Exception): 35 | pass 36 | 37 | 38 | class FamilyElement(Element): 39 | 40 | def get_tag(self): 41 | return gedcom.tags.GEDCOM_TAG_FAMILY 42 | -------------------------------------------------------------------------------- /gedcom/element/root.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Python GEDCOM Parser 4 | # 5 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com) 6 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com) 7 | # Copyright (C) 2016 Andreas Oberritter 8 | # Copyright (C) 2012 Madeleine Price Ball 9 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu) 10 | # Copyright (C) 2005 Brigham Young University 11 | # 12 | # This program is free software; you can redistribute it and/or modify 13 | # it under the terms of the GNU General Public License as published by 14 | # the Free Software Foundation; either version 2 of the License, or 15 | # (at your option) any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU General Public License along 23 | # with this program; if not, write to the Free Software Foundation, Inc., 24 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 25 | # 26 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html 27 | 28 | """Virtual GEDCOM root element containing all logical records as children""" 29 | 30 | from gedcom.element.element import Element 31 | 32 | 33 | class RootElement(Element): 34 | """Virtual GEDCOM root element containing all logical records as children""" 35 | 36 | def __init__(self, level=-1, pointer="", tag="ROOT", value="", crlf="\n", multi_line=True): 37 | super(RootElement, self).__init__(level, pointer, tag, value, crlf, multi_line) 38 | -------------------------------------------------------------------------------- /gedcom/element/object.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Python GEDCOM Parser 4 | # 5 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com) 6 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com) 7 | # Copyright (C) 2016 Andreas Oberritter 8 | # Copyright (C) 2012 Madeleine Price Ball 9 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu) 10 | # Copyright (C) 2005 Brigham Young University 11 | # 12 | # This program is free software; you can redistribute it and/or modify 13 | # it under the terms of the GNU General Public License as published by 14 | # the Free Software Foundation; either version 2 of the License, or 15 | # (at your option) any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU General Public License along 23 | # with this program; if not, write to the Free Software Foundation, Inc., 24 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 25 | # 26 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html 27 | 28 | """GEDCOM element consisting of tag `gedcom.tags.GEDCOM_TAG_OBJECT`""" 29 | 30 | from gedcom.element.element import Element 31 | import gedcom.tags 32 | 33 | 34 | class NotAnActualObjectError(Exception): 35 | pass 36 | 37 | 38 | class ObjectElement(Element): 39 | 40 | def is_object(self): 41 | """Checks if this element is an actual object 42 | :rtype: bool 43 | """ 44 | return self.get_tag() == gedcom.tags.GEDCOM_TAG_OBJECT 45 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from os import path 3 | from io import open 4 | 5 | here = path.abspath(path.dirname(__file__)) 6 | 7 | # Get the long description from the `README.md` file 8 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 9 | long_description = f.read() 10 | 11 | setup( 12 | name='python-gedcom', 13 | version='1.0.0', 14 | description='A Python module for parsing, analyzing, and manipulating GEDCOM files.', 15 | long_description=long_description, 16 | long_description_content_type='text/markdown', 17 | url='https://github.com/nickreynke/python-gedcom', 18 | author='Nicklas Reincke', 19 | author_email='contact@reynke.com', 20 | license='GPLv2', 21 | classifiers=[ 22 | 'Development Status :: 5 - Production/Stable', 23 | 'Intended Audience :: Developers', 24 | 'Topic :: Sociology :: Genealogy', 25 | 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', 26 | 'Programming Language :: Python :: 3', 27 | 'Programming Language :: Python :: 3.5', 28 | 'Programming Language :: Python :: 3.6', 29 | 'Programming Language :: Python :: 3.7', 30 | 'Programming Language :: Python :: 3.8', 31 | ], 32 | keywords='python gedcom parser', 33 | packages=find_packages(exclude=['contrib', 'docs', 'tests']), 34 | install_requires=[], 35 | extras_require={ 36 | 'dev': ['setuptools', 'wheel', 'twine', 'pdoc3'], 37 | 'test': ['tox'], 38 | }, 39 | package_data={}, 40 | data_files=[], 41 | project_urls={ 42 | 'Bug Reports': 'https://github.com/nickreynke/python-gedcom/issues', 43 | 'Source': 'https://github.com/nickreynke/python-gedcom', 44 | }, 45 | ) 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # MacOS 2 | .DS_Store 3 | 4 | # Ignore .ged files 5 | *.ged 6 | 7 | # Unignore .ged test files 8 | !tests/files/*.ged 9 | 10 | # Due to using tox 11 | .tox 12 | 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | *.py[cod] 16 | *$py.class 17 | 18 | # C extensions 19 | *.so 20 | 21 | # Distribution / packaging 22 | .Python 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | wheels/ 35 | *.egg-info/ 36 | .installed.cfg 37 | *.egg 38 | MANIFEST 39 | 40 | # PyInstaller 41 | # Usually these files are written by a python script from a template 42 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 43 | *.manifest 44 | *.spec 45 | 46 | # Installer logs 47 | pip-log.txt 48 | pip-delete-this-directory.txt 49 | 50 | # Unit test / coverage reports 51 | htmlcov/ 52 | .tox/ 53 | .coverage 54 | .coverage.* 55 | .cache 56 | nosetests.xml 57 | coverage.xml 58 | *.cover 59 | .hypothesis/ 60 | .pytest_cache/ 61 | 62 | # Translations 63 | *.mo 64 | *.pot 65 | 66 | # Django stuff: 67 | *.log 68 | local_settings.py 69 | db.sqlite3 70 | 71 | # Flask stuff: 72 | instance/ 73 | .webassets-cache 74 | 75 | # Scrapy stuff: 76 | .scrapy 77 | 78 | # Sphinx documentation 79 | docs/_build/ 80 | 81 | # PyBuilder 82 | target/ 83 | 84 | # Jupyter Notebook 85 | .ipynb_checkpoints 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # celery beat schedule file 91 | celerybeat-schedule 92 | 93 | # SageMath parsed files 94 | *.sage.py 95 | 96 | # Environments 97 | .env 98 | .venv 99 | env/ 100 | venv/ 101 | ENV/ 102 | env.bak/ 103 | venv.bak/ 104 | 105 | # Spyder project settings 106 | .spyderproject 107 | .spyproject 108 | 109 | # Rope project settings 110 | .ropeproject 111 | 112 | # mkdocs documentation 113 | /site 114 | 115 | # mypy 116 | .mypy_cache/ 117 | -------------------------------------------------------------------------------- /gedcom/helpers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Python GEDCOM Parser 4 | # 5 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com) 6 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com) 7 | # Copyright (C) 2016 Andreas Oberritter 8 | # Copyright (C) 2012 Madeleine Price Ball 9 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu) 10 | # Copyright (C) 2005 Brigham Young University 11 | # 12 | # This program is free software; you can redistribute it and/or modify 13 | # it under the terms of the GNU General Public License as published by 14 | # the Free Software Foundation; either version 2 of the License, or 15 | # (at your option) any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU General Public License along 23 | # with this program; if not, write to the Free Software Foundation, Inc., 24 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 25 | # 26 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html 27 | 28 | """ 29 | Helper methods. 30 | """ 31 | 32 | import warnings 33 | import functools 34 | 35 | 36 | def deprecated(func): 37 | """This is a decorator which can be used to mark functions 38 | as deprecated. It will result in a warning being emitted 39 | when the function is used. 40 | """ 41 | @functools.wraps(func) 42 | def new_func(*args, **kwargs): 43 | 44 | warnings.simplefilter('always', DeprecationWarning) # turn off filter 45 | warnings.warn("Call to deprecated function {}.".format(func.__name__), 46 | category=DeprecationWarning, 47 | stacklevel=2) 48 | warnings.simplefilter('default', DeprecationWarning) # reset filter 49 | 50 | return func(*args, **kwargs) 51 | 52 | return new_func 53 | -------------------------------------------------------------------------------- /tests/test_parser.py: -------------------------------------------------------------------------------- 1 | from gedcom.element.individual import IndividualElement 2 | from gedcom.element.root import RootElement 3 | from gedcom.parser import Parser 4 | 5 | 6 | def test_initialization(): 7 | parser = Parser() 8 | assert isinstance(parser, Parser) 9 | 10 | 11 | def test_invalidate_cache(): 12 | parser = Parser() 13 | parser.parse_file('tests/files/Musterstammbaum.ged') 14 | 15 | assert len(parser.get_element_list()) == 396 16 | assert len(parser.get_element_dictionary()) == 32 17 | 18 | parser.invalidate_cache() 19 | 20 | assert len(parser.get_element_list()) == 396 21 | assert len(parser.get_element_dictionary()) == 32 22 | 23 | 24 | def test_get_root_element(): 25 | parser = Parser() 26 | assert isinstance(parser.get_root_element(), RootElement) 27 | 28 | 29 | def test_parse_file(): 30 | parser = Parser() 31 | 32 | assert len(parser.get_root_child_elements()) == 0 33 | 34 | parser.parse_file('tests/files/Musterstammbaum.ged') 35 | 36 | assert len(parser.get_root_child_elements()) == 34 37 | 38 | individuals_in_root_child_elements = 0 39 | individuals_in_element_list = 0 40 | 41 | for element in parser.get_root_child_elements(): 42 | if isinstance(element, IndividualElement): 43 | individuals_in_root_child_elements += 1 44 | 45 | for element in parser.get_element_list(): 46 | if isinstance(element, IndividualElement): 47 | individuals_in_element_list += 1 48 | 49 | assert individuals_in_root_child_elements == 20 50 | assert individuals_in_element_list == 20 51 | 52 | 53 | def test_parse_from_string(): 54 | case_1 = """0 @I5@ INDI 55 | 1 NAME First /Last/ 56 | 1 SEX M 57 | 1 BIRT 58 | 2 DATE 1 JAN 1900 59 | 2 PLAC Kirkland, King, Washington, USA 60 | 3 MAP 61 | 4 LATI N47.680663 62 | 4 LONG W122.234319 63 | """ 64 | gedcom_parser = Parser() 65 | gedcom_parser.parse([(a + '\n').encode('utf-8-sig') for a in case_1.splitlines()]) 66 | element_1 = gedcom_parser.get_root_child_elements()[0] 67 | assert isinstance(element_1, IndividualElement) 68 | assert element_1.get_tag() == 'INDI' 69 | assert element_1.get_pointer() == '@I5@' 70 | element_1_children = element_1.get_child_elements() 71 | assert len(element_1_children) == 3 72 | assert element_1_children[0].get_tag() == 'NAME' 73 | assert element_1_children[1].get_tag() == 'SEX' 74 | assert element_1_children[2].get_tag() == 'BIRT' 75 | 76 | case_2 = """0 @F28@ FAM 77 | 1 HUSB @I80@ 78 | 1 WIFE @I81@ 79 | 1 CHIL @I9@ 80 | 2 _FREL Natural 81 | 2 _MREL Natural 82 | 1 CHIL @I84@ 83 | 2 _FREL Natural 84 | 2 _MREL Natural 85 | 1 CHIL @I85@ 86 | 2 _FREL Natural 87 | 2 _MREL Natural 88 | """ 89 | gedcom_parser.parse([(a + '\n').encode('utf-8-sig') for a in case_2.splitlines()]) 90 | element_2 = gedcom_parser.get_root_child_elements()[0] 91 | assert element_2.get_tag() == 'FAM' 92 | assert element_2.get_pointer() == '@F28@' 93 | element_2_children = element_2.get_child_elements() 94 | assert len(element_2_children) == 5 95 | assert element_2_children[0].get_tag() == 'HUSB' 96 | assert element_2_children[1].get_tag() == 'WIFE' 97 | assert element_2_children[2].get_tag() == 'CHIL' 98 | assert element_2_children[3].get_value() == '@I84@' 99 | 100 | 101 | def test___parse_line(): 102 | # @TODO Add appropriate testing cases 103 | pass 104 | -------------------------------------------------------------------------------- /gedcom/gedcom.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | The module can be installed via [pipenv](https://github.com/pypa/pipenv) or simply [pip](https://pip.pypa.io/). 4 | 5 | Run `pip3 install python-gedcom` to install or `pip3 install python-gedcom --upgrade` 6 | to upgrade to the newest version uploaded to the [PyPI repository](https://pypi.org/project/python-gedcom/). 7 | 8 | .. tip:: 9 | Using [pipenv](https://github.com/pypa/pipenv) simplifies the installation and maintenance of dependencies. 10 | 11 | ### Pre-releases 12 | 13 | If you want to use the latest pre-release of the `python-gedcom` package, 14 | simply append the `--pre` option to `pip3`: `pip3 install python-gedcom --pre` 15 | 16 | ## Important classes and modules 17 | 18 | * `gedcom.parser.Parser`: The actual GEDCOM parser. 19 | * `gedcom.tags`: GEDCOM tags like `gedcom.tags.GEDCOM_TAG_INDIVIDUAL` (`INDI`) or `gedcom.tags.GEDCOM_TAG_NAME` (`NAME`) 20 | * `gedcom.element`: Contains all relevant elements generated by a `gedcom.parser.Parser`. 21 | 22 | ## Example usage 23 | 24 | When successfully installed you may import the `gedcom` package and use it like so: 25 | 26 | ```python 27 | from gedcom.element.individual import IndividualElement 28 | from gedcom.parser import Parser 29 | 30 | # Path to your ".ged" file 31 | file_path = '' 32 | 33 | # Initialize the parser 34 | gedcom_parser = Parser() 35 | 36 | # Parse your file 37 | gedcom_parser.parse_file(file_path) 38 | 39 | root_child_elements = gedcom_parser.get_root_child_elements() 40 | 41 | # Iterate through all root child elements 42 | for element in root_child_elements: 43 | 44 | # Is the "element" an actual "IndividualElement"? (Allows usage of extra functions such as "surname_match" and "get_name".) 45 | if isinstance(element, IndividualElement): 46 | 47 | # Get all individuals whose surname matches "Doe" 48 | if element.surname_match('Doe'): 49 | 50 | # Unpack the name tuple 51 | (first, last) = element.get_name() 52 | 53 | # Print the first and last name of the found individual 54 | print(first + " " + last) 55 | ``` 56 | 57 | .. tip:: 58 | Please have a look at the test files found in the 59 | [`tests/` directory in the source code on GitHub](https://github.com/nickreynke/python-gedcom/tree/master/tests). 60 | 61 | ## Strict parsing 62 | 63 | Large sites like Ancestry and MyHeritage (among others) don't always produce perfectly formatted GEDCOM files. 64 | If you encounter errors in parsing, you might consider disabling strict parsing which is enabled by default: 65 | 66 | ```python 67 | from gedcom.parser import Parser 68 | 69 | file_path = '' # Path to your `.ged` file 70 | 71 | gedcom_parser = Parser() 72 | gedcom_parser.parse_file(file_path, False) # Disable strict parsing 73 | ``` 74 | 75 | Disabling strict parsing will allow the parser to gracefully handle the following quirks: 76 | 77 | - Multi-line fields that don't use `CONC` or `CONT` 78 | - Handle the last line not ending in a CRLF (`\r\n`) 79 | 80 | ## License 81 | 82 | Licensed under the [GNU General Public License v2](http://www.gnu.org/licenses/gpl-2.0.html) 83 | 84 | **Python GEDCOM Parser** 85 |
Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com) 86 |
Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com) 87 |
Copyright (C) 2016 Andreas Oberritter 88 |
Copyright (C) 2012 Madeleine Price Ball 89 |
Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu) 90 |
Copyright (C) 2005 Brigham Young University 91 | 92 | This program is free software; you can redistribute it and/or modify 93 | it under the terms of the GNU General Public License as published by 94 | the Free Software Foundation; either version 2 of the License, or 95 | (at your option) any later version. 96 | 97 | This program is distributed in the hope that it will be useful, 98 | but WITHOUT ANY WARRANTY; without even the implied warranty of 99 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 100 | GNU General Public License for more details. 101 | 102 | You should have received a copy of the GNU General Public License along 103 | with this program; if not, write to the Free Software Foundation, Inc., 104 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 105 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Python GEDCOM Parser - Changelog 2 | 3 | ## [v1.0.0](https://github.com/nickreynke/python-gedcom/releases/tag/v1.0.0) 4 | 5 | ### Changes: 6 | 7 | - Added `CHANGELOG.md` 8 | - Set source code encoding to UTF-8 explicitly, since for Python 2 ASCII would be the default. For Python 3 the default is UTF-8. 9 | - Separated code of `__init__.py` into individual files, updating the package structure ([#15](https://github.com/nickreynke/python-gedcom/issues/15)) 10 | - Resulted in a new `parser.py` file containing the actual parser, a `element.py` containing the elements the parser can parse and a `tags.py` containing the used GEDCOM tags 11 | - Separated code of new `element.py` into individual modules extending the `Element` class within new submodule `element` to better 12 | differentiate between the type of `Element`s the parser parses (e.g. `FamilyElement`, `IndividualElement`, ...) 13 | - Added `helpers.py` containing helper functions like a `@deprecated` annotation to annotate methods or classes as 14 | deprecated. (The helpers may be removed when its contents are no longer used within the project.) 15 | - GEDCOM file is no longer parsed within constructor of `Parser` class (the actual parser; it was named `Gedcom` before). 16 | The new way to parse is as follows: 17 | ```python 18 | # from gedcom import Gedcom <- Old way of importing the parser 19 | from gedcom.parser import Parser 20 | 21 | file_path = '' # Path to your `.ged` file 22 | 23 | # The old way would be to supply the `Parser` (or `Gedcom`) class the `file_path` as its first parameter on initialization 24 | gedcom_parser = Parser() 25 | 26 | # The new way to parse a GEDCOM file 27 | gedcom_parser.parse_file(file_path) 28 | 29 | # ... 30 | ``` 31 | 32 | ### Deprecations: 33 | 34 | - `get_individual()` method within `Element` class. Use `to_gedcom_string()` instead. 35 | - `given_match()` method within `IndividualElement` class. Use `given_name_match()` instead. 36 | - `get_burial()` method within `IndividualElement` class. Use `get_burial_data()` instead. 37 | - `get_burial()` method within `IndividualElement` class. Use `get_burial_data()` instead. 38 | - `get_census()` method within `IndividualElement` class. Use `get_census_data()` instead. 39 | 40 | ### Migrating from v0.2.x to v1.0.0: 41 | 42 | The old way of importing the `gedcom` package was like this: `from gedcom import Gedcom`. 43 | 44 | The new package code is separated into individual modules within the package. So `Parser` (the actual parser which was named `Gedcom`) would be imported like this: 45 | `from gedcom.parser import Parser`, since the `Parser` class lies within the module `parser` within the package `gedcom`. 46 | 47 | Same procedure for the `Element` class: `from gedcom.element.element import Element`, since the `Element` class lies 48 | within the package `gedcom`, the subpackage `element` and the module `element`. 49 | 50 | This allows for better maintainability and scalability. 51 | 52 | If there are any questions or you encounter a bug please open an issue [here](https://github.com/nickreynke/python-gedcom/issues). 53 | 54 | ## [v0.2.5dev](https://github.com/nickreynke/python-gedcom/releases/tag/v0.2.5dev) 55 | 56 | - Updated project structure ([#18](https://github.com/nickreynke/python-gedcom/issues/18)) 57 | - Fixed `setup.py` outputting correct markdown when reading the `README.md` ([#16](https://github.com/nickreynke/python-gedcom/issues/16)) 58 | - Applied Flake8 code style and **added explicit error handling** 59 | - Set up test suite 60 | 61 | ## [v0.2.4dev](https://github.com/nickreynke/python-gedcom/releases/tag/v0.2.4dev) 62 | 63 | - Made `surname_match` and `given_match` case insensitive ([#10](https://github.com/nickreynke/python-gedcom/issues/10)) 64 | - Added new `is_child` method ([#10](https://github.com/nickreynke/python-gedcom/issues/10)) 65 | 66 | ## [v0.2.3dev](https://github.com/nickreynke/python-gedcom/releases/tag/v0.2.3dev) 67 | 68 | - Assemble marriages properly ([#9](https://github.com/nickreynke/python-gedcom/issues/9)) 69 | - Return the top NAME record instead of the last one ([#9](https://github.com/nickreynke/python-gedcom/issues/9)) 70 | 71 | ## [v0.2.2dev](https://github.com/nickreynke/python-gedcom/releases/tag/v0.2.2dev) 72 | 73 | - Support BOM control characters ([#5](https://github.com/nickreynke/python-gedcom/issues/5)) 74 | - Support the last line not having a CR and/or LF 75 | - Support incorrect line splitting generated by Ancestry. Insert CONT/CONC tag as necessary ([#6](https://github.com/nickreynke/python-gedcom/issues/6)) 76 | 77 | ## [v0.2.1dev](https://github.com/nickreynke/python-gedcom/releases/tag/v0.2.1dev) 78 | 79 | - Changed broken links to GEDCOM format specification ([#2](https://github.com/nickreynke/python-gedcom/issues/2)) 80 | 81 | ## [v0.2.0dev](https://github.com/nickreynke/python-gedcom/releases/tag/v0.2.0dev) 82 | 83 | - Added `develop` branch to track and update current development process 84 | - Applied PEP 8 Style Guide conventions 85 | - **Renamed variables and methods** to make their purpose more clear 86 | - **Outsourced GEDCOM tags to module level** 87 | - **Added missing inline documentation** 88 | - Optimized `README.md` (sections and better description) 89 | - Added `LICENSE` file 90 | - Cleaned up and optimized code 91 | 92 | ## [v0.1.1dev](https://github.com/nickreynke/python-gedcom/releases/tag/v0.1.1dev) 93 | 94 | - initial release; [forked](https://github.com/madprime/python-gedcom) 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | PyPI 7 | GitHub release 8 | Build Status 9 | GEDCOM format version 5.5 10 | Python versions 3.5 to 3.8 11 |

12 | 13 |

14 | A Python module for parsing, analyzing, and manipulating GEDCOM files. 15 |

16 | 17 |

18 | GEDCOM files contain ancestry data. The parser is currently supporting the GEDCOM 5.5 format which is detailed 19 | here. 20 |

21 | 22 | ## Documentation 23 | 24 | Documentation can be found here: https://nickreynke.github.io/python-gedcom/gedcom/index.html 25 | 26 | ## Changelog 27 | 28 | For the latest changes please have a look at the [`CHANGELOG.md`](CHANGELOG.md) file. 29 | 30 | The current development process can be tracked in the [develop branch](https://github.com/nickreynke/python-gedcom/tree/develop). 31 | 32 | ## Common problems 33 | 34 | * When you name your script `gedcom.py`, and import the `gedcom` module from this package, running your script won't 35 | work because Python will try to resolve imports like `gedcom.element.individual` from within your `gedcom.py` but 36 | not from within the module from this package. Rename your file in this case. ([#26](https://github.com/nickreynke/python-gedcom/issues/26)) 37 | 38 | ## Local development 39 | 40 | Local development is done using [pyenv](https://github.com/pyenv/pyenv) and 41 | [pipenv](https://github.com/pypa/pipenv) using Python 3.5. 42 | 43 | ### Running tests 44 | 45 | 1. Run `pipenv install -d` to install normal and dev dependencies 46 | 1. Run tests with [tox](https://tox.readthedocs.io/en/latest/index.html) (`pipenv run tox` in your console) 47 | * For Python 3.5 run `pipenv run tox -e py35` (you need to have Python 3.5 installed) 48 | * For Python 3.6 run `pipenv run tox -e py36` (you need to have Python 3.6 installed) 49 | * For Python 3.7 run `pipenv run tox -e py37` (you need to have Python 3.7 installed) 50 | * For Python 3.8 run `pipenv run tox -e py38` (you need to have Python 3.8 installed) 51 | 52 | ### Generating docs 53 | 54 | 1. Run `pipenv install -d` to install normal and dev dependencies 55 | 1. Run `pipenv run pdoc3 --html -o docs/ gedcom --force` to generate docs into the `docs/` directory 56 | 57 | > To develop docs run `pipenv run pdoc3 --http localhost:8000 gedcom` 58 | > to watch files and instantly see changes in your browser under http://localhost:8000. 59 | 60 | ### Uploading a new package to PyPI 61 | 62 | 1. Run `pipenv install -d` to install normal and dev dependencies 63 | 1. Run `pipenv run python3 setup.py sdist bdist_wheel` to generate distribution archives 64 | 1. Run `pipenv run twine upload --repository-url https://test.pypi.org/legacy/ dist/*` to upload the archives to the Test Python Package Index repository 65 | 66 | > When the package is ready to be published to the real Python Package Index 67 | the `repository-url` is `https://upload.pypi.org/legacy/`. 68 | > 69 | > `pipenv run twine upload --repository-url https://upload.pypi.org/legacy/ dist/*` 70 | 71 | ## History 72 | 73 | This module was originally based on a GEDCOM parser written by 74 | Daniel Zappala at Brigham Young University (Copyright (C) 2005) which 75 | was licensed under the GPL v2 and then continued by 76 | [Mad Price Ball](https://github.com/madprime) in 2012. 77 | 78 | The project was taken over by [Nicklas Reincke](https://github.com/nickreynke) in 2018. 79 | Together with [Damon Brodie](https://github.com/nomadyow) a lot of changes were made and the parser was optimized. 80 | 81 | ## License 82 | 83 | Licensed under the [GNU General Public License v2](http://www.gnu.org/licenses/gpl-2.0.html) 84 | 85 | **Python GEDCOM Parser** 86 |
Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com) 87 |
Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com) 88 |
Copyright (C) 2016 Andreas Oberritter 89 |
Copyright (C) 2012 Madeleine Price Ball 90 |
Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu) 91 |
Copyright (C) 2005 Brigham Young University 92 | 93 | This program is free software; you can redistribute it and/or modify 94 | it under the terms of the GNU General Public License as published by 95 | the Free Software Foundation; either version 2 of the License, or 96 | (at your option) any later version. 97 | 98 | This program is distributed in the hope that it will be useful, 99 | but WITHOUT ANY WARRANTY; without even the implied warranty of 100 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 101 | GNU General Public License for more details. 102 | 103 | You should have received a copy of the GNU General Public License along 104 | with this program; if not, write to the Free Software Foundation, Inc., 105 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 106 | -------------------------------------------------------------------------------- /gedcom/tags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Python GEDCOM Parser 4 | # 5 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com) 6 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com) 7 | # Copyright (C) 2016 Andreas Oberritter 8 | # Copyright (C) 2012 Madeleine Price Ball 9 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu) 10 | # Copyright (C) 2005 Brigham Young University 11 | # 12 | # This program is free software; you can redistribute it and/or modify 13 | # it under the terms of the GNU General Public License as published by 14 | # the Free Software Foundation; either version 2 of the License, or 15 | # (at your option) any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU General Public License along 23 | # with this program; if not, write to the Free Software Foundation, Inc., 24 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 25 | # 26 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html 27 | 28 | """ 29 | GEDCOM tags. 30 | """ 31 | 32 | GEDCOM_PROGRAM_DEFINED_TAG_MREL = "_MREL" 33 | """Value: `_MREL` 34 | 35 | Relationship to a mother.""" 36 | 37 | GEDCOM_PROGRAM_DEFINED_TAG_FREL = "_FREL" 38 | """Value: `_FREL` 39 | 40 | Relationship to a father.""" 41 | 42 | GEDCOM_TAG_BIRTH = "BIRT" 43 | """Value: `BIRT` 44 | 45 | The event of entering into life.""" 46 | 47 | GEDCOM_TAG_BURIAL = "BURI" 48 | """Value: `BURI` 49 | 50 | The event of the proper disposing of the mortal remains of a deceased person.""" 51 | 52 | GEDCOM_TAG_CENSUS = "CENS" 53 | """Value: `CENS`. 54 | 55 | The event of the periodic count of the population for a designated locality, such as a national or state Census.""" 56 | 57 | GEDCOM_TAG_CHANGE = "CHAN" 58 | """Value: `CHAN` 59 | 60 | Indicates a change, correction, or modification. Typically used in connection 61 | with a `gedcom.tags.GEDCOM_TAG_DATE` to specify when a change in information occurred.""" 62 | 63 | GEDCOM_TAG_CHILD = "CHIL" 64 | """Value: `CHIL` 65 | 66 | The natural, adopted, or sealed (LDS) child of a father and a mother.""" 67 | 68 | GEDCOM_TAG_CONCATENATION = "CONC" 69 | """Value: `CONC` 70 | 71 | An indicator that additional data belongs to the superior value. The information from the `CONC` value is to 72 | be connected to the value of the superior preceding line without a space and without a carriage return and/or 73 | new line character. Values that are split for a `CONC` tag must always be split at a non-space. If the value is 74 | split on a space the space will be lost when concatenation takes place. This is because of the treatment that 75 | spaces get as a GEDCOM delimiter, many GEDCOM values are trimmed of trailing spaces and some systems look for 76 | the first non-space starting after the tag to determine the beginning of the value.""" 77 | 78 | GEDCOM_TAG_CONTINUED = "CONT" 79 | """Value: `CONT` 80 | 81 | An indicator that additional data belongs to the superior value. The information from the `CONT` value is to be 82 | connected to the value of the superior preceding line with a carriage return and/or new line character. 83 | Leading spaces could be important to the formatting of the resultant text. When importing values from `CONT` lines 84 | the reader should assume only one delimiter character following the `CONT` tag. Assume that the rest of the leading 85 | spaces are to be a part of the value.""" 86 | 87 | GEDCOM_TAG_DATE = "DATE" 88 | """Value: `DATE` 89 | 90 | The time of an event in a calendar format.""" 91 | 92 | GEDCOM_TAG_DEATH = "DEAT" 93 | """Value: `DEAT` 94 | 95 | The event when mortal life terminates.""" 96 | 97 | GEDCOM_TAG_FAMILY = "FAM" 98 | """Value: `FAM`. 99 | 100 | Identifies a legal, common law, or other customary relationship of man and woman and their children, 101 | if any, or a family created by virtue of the birth of a child to its biological father and mother.""" 102 | 103 | GEDCOM_TAG_FAMILY_CHILD = "FAMC" 104 | """Value: `FAMC` 105 | 106 | Identifies the family in which an individual appears as a child.""" 107 | 108 | GEDCOM_TAG_FAMILY_SPOUSE = "FAMS" 109 | """Value: `FAMS` 110 | 111 | Identifies the family in which an individual appears as a spouse.""" 112 | 113 | GEDCOM_TAG_FILE = "FILE" 114 | """Value: `FILE` 115 | 116 | An information storage place that is ordered and arranged for preservation and reference.""" 117 | 118 | GEDCOM_TAG_GIVEN_NAME = "GIVN" 119 | """Value: `GIVN` 120 | 121 | A given or earned name used for official identification of a person.""" 122 | 123 | GEDCOM_TAG_HUSBAND = "HUSB" 124 | """Value: `HUSB` 125 | 126 | An individual in the family role of a married man or father.""" 127 | 128 | GEDCOM_TAG_INDIVIDUAL = "INDI" 129 | """Value: `INDI` 130 | 131 | A person.""" 132 | 133 | GEDCOM_TAG_MARRIAGE = "MARR" 134 | """Value: `MARR`. 135 | 136 | A legal, common-law, or customary event of creating a family unit of a man and a woman as husband and wife.""" 137 | 138 | GEDCOM_TAG_NAME = "NAME" 139 | """Value: `NAME`. 140 | 141 | A word or combination of words used to help identify an individual, title, or other item. 142 | More than one NAME line should be used for people who were known by multiple names.""" 143 | 144 | GEDCOM_TAG_OBJECT = "OBJE" 145 | """Value: `OBJE` 146 | 147 | Pertaining to a grouping of attributes used in describing something. Usually referring to the data required 148 | to represent a multimedia object, such an audio recording, a photograph of a person, or an image of a document.""" 149 | 150 | GEDCOM_TAG_OCCUPATION = "OCCU" 151 | """Value: `OCCU` 152 | 153 | The type of work or profession of an individual.""" 154 | 155 | GEDCOM_TAG_PLACE = "PLAC" 156 | """Value: `PLAC` 157 | 158 | A jurisdictional name to identify the place or location of an event.""" 159 | 160 | GEDCOM_TAG_PRIVATE = "PRIV" 161 | """Value: `PRIV` 162 | 163 | Flag for private address or event.""" 164 | 165 | GEDCOM_TAG_SEX = "SEX" 166 | """Value: `SEX` 167 | 168 | Indicates the sex of an individual--male or female.""" 169 | 170 | GEDCOM_TAG_SOURCE = "SOUR" 171 | """Value: `SOUR` 172 | 173 | The initial or original material from which information was obtained.""" 174 | 175 | GEDCOM_TAG_SURNAME = "SURN" 176 | """Value: `SURN` 177 | 178 | A family name passed on or used by members of a family.""" 179 | 180 | GEDCOM_TAG_WIFE = "WIFE" 181 | """Value: `WIFE` 182 | 183 | An individual in the role as a mother and/or married woman.""" 184 | -------------------------------------------------------------------------------- /tests/files/Musterstammbaum.ged: -------------------------------------------------------------------------------- 1 | 0 HEAD 2 | 1 SOUR AHN 3 | 2 VERS 2.99g 4 | 2 NAME Ahnenblatt 5 | 2 CORP Dirk Boettcher 6 | 1 DATE 15 MAR 2019 7 | 2 TIME 19:34:53 8 | 1 SUBM @SUBM@ 9 | 1 FILE Musterstammbaum.ged 10 | 1 GEDC 11 | 2 VERS 5.5.1 12 | 2 FORM LINEAGE-LINKED 13 | 1 CHAR UTF-8 14 | 1 _NAVM 1 15 | 2 _NAVI @1@ 16 | 1 _HOME @1@ 17 | 0 @1@ INDI 18 | 1 _UID 4AFC62659C6D40708AE17246F3579AB5828B 19 | 1 NAME Max /Mustermann/ 20 | 2 SURN Mustermann 21 | 2 GIVN Max 22 | 1 SEX M 23 | 1 BIRT 24 | 2 DATE 1 JAN 1980 25 | 2 PLAC Musterstadt 26 | 1 CHAN 27 | 2 DATE 15 MAR 2019 28 | 3 TIME 19:34:31 29 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 30 | 1 FAMS @F10@ 31 | 1 FAMC @F8@ 32 | 0 @2@ INDI 33 | 1 _UID 0D3458099F444410BD8CA4227EAE09789583 34 | 1 FAMS @F10@ 35 | 1 NAME Florian /Mustertyp/ 36 | 2 SURN Mustertyp 37 | 2 GIVN Florian 38 | 1 SEX M 39 | 1 BIRT 40 | 2 DATE 27 JAN 1982 41 | 2 PLAC Musterhausen 42 | 1 CHAN 43 | 2 DATE 18 DEC 2018 44 | 3 TIME 19:29:51 45 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 46 | 1 FAMC @F11@ 47 | 0 @3@ INDI 48 | 1 _UID DD6E84DA500F40A48AC6830853A0DD841B93 49 | 1 FAMS @F8@ 50 | 1 NAME Gudwin /Mustermann/ 51 | 2 SURN Mustermann 52 | 2 GIVN Gudwin 53 | 1 SEX M 54 | 1 BIRT 55 | 2 DATE 25 OCT 1950 56 | 2 PLAC Musterdorf 57 | 1 CHAN 58 | 2 DATE 15 MAR 2019 59 | 3 TIME 19:34:36 60 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 61 | 1 FAMC @F1@ 62 | 0 @4@ INDI 63 | 1 _UID 41299385739D48DCB35818D7DB79A7C26D9B 64 | 1 NAME Gisela /Musterfrau/ 65 | 2 SURN Musterfrau 66 | 2 GIVN Gisela 67 | 1 SEX F 68 | 1 BIRT 69 | 2 DATE 14 SEP 1954 70 | 2 PLAC Musterstadt 71 | 1 CHAN 72 | 2 DATE 18 DEC 2018 73 | 3 TIME 19:34:13 74 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 75 | 1 FAMS @F8@ 76 | 1 FAMC @F9@ 77 | 0 @5@ INDI 78 | 1 _UID 874E1342BCC74B40A6260804BD542079BA18 79 | 1 FAMS @F1@ 80 | 1 FAMS @F7@ 81 | 1 NAME Theodor /Mustermann/ 82 | 2 SURN Mustermann 83 | 2 GIVN Theodor 84 | 1 SEX M 85 | 1 BIRT 86 | 2 DATE 5 AUG 1930 87 | 2 PLAC Musterdorf 88 | 1 CHAN 89 | 2 DATE 15 MAR 2019 90 | 3 TIME 19:34:31 91 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 92 | 0 @6@ INDI 93 | 1 _UID A7E0D8AB419A415A8AE0B164C9397E2CABEE 94 | 1 NAME Anna /Musterberg/ 95 | 2 SURN Musterberg 96 | 2 GIVN Anna 97 | 1 SEX F 98 | 1 BIRT 99 | 2 DATE 9 SEP 1932 100 | 2 PLAC Musterstadt 101 | 1 CHAN 102 | 2 DATE 18 DEC 2018 103 | 3 TIME 19:44:48 104 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 105 | 1 DEAT 106 | 2 DATE 26 OCT 1960 107 | 2 PLAC Musterdorf 108 | 1 FAMS @F1@ 109 | 1 FAMC @F4@ 110 | 0 @7@ INDI 111 | 1 _UID 2345F735623243F19D6B1DA20F048F1BE071 112 | 1 NAME Heidrun /Musterfrau/ 113 | 2 SURN Musterfrau 114 | 2 GIVN Heidrun 115 | 1 SEX F 116 | 1 BIRT 117 | 2 DATE 9 DEC 1935 118 | 2 PLAC Musterstadt 119 | 1 CHAN 120 | 2 DATE 18 DEC 2018 121 | 3 TIME 19:34:17 122 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 123 | 1 FAMS @F9@ 124 | 0 @8@ INDI 125 | 1 _UID 80FD74DB1119457DB01358D0C86A6A609FF7 126 | 1 FAMS @F9@ 127 | 1 NAME Alwin /Musterstern/ 128 | 2 SURN Musterstern 129 | 2 GIVN Alwin 130 | 1 SEX M 131 | 1 BIRT 132 | 2 DATE 6 FEB 1925 133 | 2 PLAC Musterdorf 134 | 1 CHAN 135 | 2 DATE 15 MAR 2019 136 | 3 TIME 19:32:09 137 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 138 | 0 @9@ INDI 139 | 1 _UID 768A3A5E01584861A566E0F60E66A1730394 140 | 1 FAMS @F11@ 141 | 1 NAME August /Mustertyp/ 142 | 2 SURN Mustertyp 143 | 2 GIVN August 144 | 1 SEX M 145 | 1 BIRT 146 | 2 DATE 17 JAN 1954 147 | 2 PLAC Musterhausen 148 | 1 CHAN 149 | 2 DATE 18 DEC 2018 150 | 3 TIME 19:36:05 151 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 152 | 1 FAMC @F3@ 153 | 0 @10@ INDI 154 | 1 _UID A7CAE71B3BF840F9A313340E17B0041DBFA0 155 | 1 NAME Anneliese /Musterstift/ 156 | 2 SURN Musterstift 157 | 2 GIVN Anneliese 158 | 1 SEX F 159 | 1 BIRT 160 | 2 DATE 26 SEP 1960 161 | 2 PLAC Musterhausen 162 | 1 CHAN 163 | 2 DATE 18 DEC 2018 164 | 3 TIME 19:45:41 165 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 166 | 1 FAMS @F11@ 167 | 1 FAMC @F6@ 168 | 0 @11@ INDI 169 | 1 _UID 4D043CD27DC14A5FA77316D9397EA46D1791 170 | 1 FAMS @F3@ 171 | 1 NAME August /Mustertyp/ 172 | 2 SURN Mustertyp 173 | 2 GIVN August 174 | 1 SEX M 175 | 1 BIRT 176 | 2 DATE 7 JUL 1931 177 | 2 PLAC Musterdorf 178 | 1 CHAN 179 | 2 DATE 18 DEC 2018 180 | 3 TIME 19:38:05 181 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 182 | 0 @12@ INDI 183 | 1 _UID 6BAEBB406B64428F84B3F95BE3401635AD96 184 | 1 NAME Bertha /Musterberg/ 185 | 2 SURN Musterberg 186 | 2 GIVN Bertha 187 | 1 SEX F 188 | 1 BIRT 189 | 2 DATE 29 JAN 1934 190 | 2 PLAC Musterstadt 191 | 1 CHAN 192 | 2 DATE 18 DEC 2018 193 | 3 TIME 19:40:09 194 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 195 | 1 FAMS @F3@ 196 | 1 FAMC @F2@ 197 | 0 @13@ INDI 198 | 1 _UID 22C93CE9E8C5466D88CE4C8BC4C37CE78785 199 | 1 NAME Johann /Musterberg/ 200 | 2 SURN Musterberg 201 | 2 GIVN Johann 202 | 1 SEX M 203 | 1 BIRT 204 | 2 DATE 1 JAN 1900 205 | 2 PLAC Musterstadt 206 | 1 CHAN 207 | 2 DATE 18 DEC 2018 208 | 3 TIME 19:39:13 209 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 210 | 1 DEAT 211 | 2 DATE 27 FEB 1972 212 | 2 PLAC Musterdorf 213 | 1 FAMS @F4@ 214 | 1 FAMC @F5@ 215 | 0 @14@ INDI 216 | 1 _UID 4E97FC61322045718B3DEB1F1AC76FFE6A8A 217 | 1 FAMS @F4@ 218 | 1 NAME Johanna /Musterfrau/ 219 | 2 SURN Musterfrau 220 | 2 GIVN Johanna 221 | 1 SEX F 222 | 1 BIRT 223 | 2 DATE 7 FEB 1900 224 | 2 PLAC Musterdorf 225 | 1 CHAN 226 | 2 DATE 18 DEC 2018 227 | 3 TIME 19:39:42 228 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 229 | 1 DEAT 230 | 2 DATE 29 AUG 1985 231 | 2 PLAC Musterstadt 232 | 0 @15@ INDI 233 | 1 _UID 096BDC0CBD0A4780812442654FFBAB16412B 234 | 1 NAME August /Musterberg/ 235 | 2 SURN Musterberg 236 | 2 GIVN August 237 | 1 SEX M 238 | 1 BIRT 239 | 2 DATE 28 FEB 1898 240 | 2 PLAC Musterstadt 241 | 1 CHAN 242 | 2 DATE 18 DEC 2018 243 | 3 TIME 19:41:29 244 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 245 | 1 DEAT 246 | 2 DATE 9 SEP 1956 247 | 2 PLAC Musterdorf 248 | 1 FAMS @F2@ 249 | 1 FAMC @F5@ 250 | 0 @16@ INDI 251 | 1 _UID 775D059819D1426F8224EC1748FDF5E4D36F 252 | 1 FAMS @F2@ 253 | 1 NAME Johanna /Mustereisen/ 254 | 2 SURN Mustereisen 255 | 2 GIVN Johanna 256 | 1 SEX F 257 | 1 BIRT 258 | 2 DATE 11 NOV 1899 259 | 2 PLAC Musterstadt 260 | 1 CHAN 261 | 2 DATE 18 DEC 2018 262 | 3 TIME 19:41:42 263 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 264 | 1 DEAT 265 | 2 DATE 28 APR 1958 266 | 2 PLAC Musterdorf 267 | 0 @17@ INDI 268 | 1 _UID DC15ED303616423DBE580F181A5BF18D09A6 269 | 1 NAME Theodor /Musterberg/ 270 | 2 SURN Musterberg 271 | 2 GIVN Theodor 272 | 1 SEX M 273 | 1 BIRT 274 | 2 DATE 26 NOV 1872 275 | 2 PLAC Musterdorf 276 | 1 CHAN 277 | 2 DATE 18 DEC 2018 278 | 3 TIME 19:42:52 279 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 280 | 1 DEAT 281 | 2 DATE 17 NOV 1939 282 | 2 PLAC Musterstadt 283 | 1 FAMS @F5@ 284 | 0 @18@ INDI 285 | 1 _UID 8B55DD693E6F427B895B0CF73772DA3731A7 286 | 1 FAMS @F5@ 287 | 1 NAME Sophie /Musterstern/ 288 | 2 SURN Musterstern 289 | 2 GIVN Sophie 290 | 1 SEX F 291 | 1 BIRT 292 | 2 DATE 1 JAN 1877 293 | 2 PLAC Musterhausen 294 | 1 CHAN 295 | 2 DATE 18 DEC 2018 296 | 3 TIME 19:42:32 297 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 298 | 1 DEAT 299 | 2 DATE 15 MAY 1910 300 | 2 PLAC Musterdorf 301 | 0 @19@ INDI 302 | 1 _UID 25491E5CE1474358AFEC39A73DAF94B45A98 303 | 1 FAMS @F6@ 304 | 1 NAME Johann /Musterstift/ 305 | 2 SURN Musterstift 306 | 2 GIVN Johann 307 | 1 SEX M 308 | 1 BIRT 309 | 2 DATE 29 SEP 1930 310 | 2 PLAC Musterhausen 311 | 1 CHAN 312 | 2 DATE 18 DEC 2018 313 | 3 TIME 19:44:36 314 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 315 | 1 DEAT 316 | 2 DATE 1961 317 | 2 PLAC Musterdorf 318 | 0 @20@ INDI 319 | 1 _UID 3D34CBD909B547C1B2A3040B6D1E3DB6BD4F 320 | 1 NAME Anneliese /Mustereisen/ 321 | 2 SURN Mustereisen 322 | 2 GIVN Anneliese 323 | 1 SEX F 324 | 1 BIRT 325 | 2 DATE 28 JAN 1930 326 | 2 PLAC Musterdorf 327 | 1 CHAN 328 | 2 DATE 18 DEC 2018 329 | 3 TIME 19:45:30 330 | 1 SOUR Stammbaum der Familie Mustermann und Mustertyp, 2018 331 | 1 FAMS @F6@ 332 | 1 FAMS @F7@ 333 | 0 @F1@ FAM 334 | 1 HUSB @5@ 335 | 1 WIFE @6@ 336 | 1 CHIL @3@ 337 | 0 @F2@ FAM 338 | 1 HUSB @15@ 339 | 1 WIFE @16@ 340 | 1 CHIL @12@ 341 | 0 @F3@ FAM 342 | 1 HUSB @11@ 343 | 1 WIFE @12@ 344 | 1 CHIL @9@ 345 | 0 @F4@ FAM 346 | 1 HUSB @13@ 347 | 1 WIFE @14@ 348 | 1 CHIL @6@ 349 | 0 @F5@ FAM 350 | 1 HUSB @17@ 351 | 1 WIFE @18@ 352 | 1 CHIL @15@ 353 | 1 CHIL @13@ 354 | 0 @F6@ FAM 355 | 1 HUSB @19@ 356 | 1 WIFE @20@ 357 | 1 MARR 358 | 2 DATE 11 SEP 1959 359 | 2 PLAC Musterhausen 360 | 1 CHIL @10@ 361 | 0 @F7@ FAM 362 | 1 HUSB @5@ 363 | 1 WIFE @20@ 364 | 1 MARR 365 | 2 DATE 11 NOV 1977 366 | 2 PLAC Musterhausen 367 | 0 @F8@ FAM 368 | 1 HUSB @3@ 369 | 1 WIFE @4@ 370 | 1 MARR 371 | 2 DATE 7 JUL 1975 372 | 2 PLAC Musterstadt 373 | 1 CHIL @1@ 374 | 0 @F9@ FAM 375 | 1 HUSB @8@ 376 | 1 WIFE @7@ 377 | 1 _STAT NOT MARRIED 378 | 1 _MARR N 379 | 1 CHIL @4@ 380 | 0 @F10@ FAM 381 | 1 HUSB @1@ 382 | 1 WIFE @2@ 383 | 1 _TYPE same-sex 384 | 1 MARR 385 | 2 DATE 18 DEC 2018 386 | 2 PLAC Musterstadt 387 | 0 @F11@ FAM 388 | 1 HUSB @9@ 389 | 1 WIFE @10@ 390 | 1 MARR 391 | 2 DATE 1 NOV 1980 392 | 2 PLAC Musterhausen 393 | 1 CHIL @2@ 394 | 0 @SUBM@ SUBM 395 | 1 NAME Mustermann 396 | 0 TRLR 397 | -------------------------------------------------------------------------------- /docs/gedcom/helpers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | gedcom.helpers API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

Module gedcom.helpers

21 |
22 |
23 |

Helper methods.

24 |
25 | 26 | Expand source code 27 | 28 |
# -*- coding: utf-8 -*-
 29 | 
 30 | # Python GEDCOM Parser
 31 | #
 32 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com)
 33 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com)
 34 | # Copyright (C) 2016 Andreas Oberritter
 35 | # Copyright (C) 2012 Madeleine Price Ball
 36 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu)
 37 | # Copyright (C) 2005 Brigham Young University
 38 | #
 39 | # This program is free software; you can redistribute it and/or modify
 40 | # it under the terms of the GNU General Public License as published by
 41 | # the Free Software Foundation; either version 2 of the License, or
 42 | # (at your option) any later version.
 43 | #
 44 | # This program is distributed in the hope that it will be useful,
 45 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
 46 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 47 | # GNU General Public License for more details.
 48 | #
 49 | # You should have received a copy of the GNU General Public License along
 50 | # with this program; if not, write to the Free Software Foundation, Inc.,
 51 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 52 | #
 53 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html
 54 | 
 55 | """
 56 | Helper methods.
 57 | """
 58 | 
 59 | import warnings
 60 | import functools
 61 | 
 62 | 
 63 | def deprecated(func):
 64 |     """This is a decorator which can be used to mark functions
 65 |     as deprecated. It will result in a warning being emitted
 66 |     when the function is used.
 67 |     """
 68 |     @functools.wraps(func)
 69 |     def new_func(*args, **kwargs):
 70 | 
 71 |         warnings.simplefilter('always', DeprecationWarning)  # turn off filter
 72 |         warnings.warn("Call to deprecated function {}.".format(func.__name__),
 73 |                       category=DeprecationWarning,
 74 |                       stacklevel=2)
 75 |         warnings.simplefilter('default', DeprecationWarning)  # reset filter
 76 | 
 77 |         return func(*args, **kwargs)
 78 | 
 79 |     return new_func
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |

Functions

88 |
89 |
90 | def deprecated(func) 91 |
92 |
93 |

This is a decorator which can be used to mark functions 94 | as deprecated. It will result in a warning being emitted 95 | when the function is used.

96 |
97 | 98 | Expand source code 99 | 100 |
def deprecated(func):
101 |     """This is a decorator which can be used to mark functions
102 |     as deprecated. It will result in a warning being emitted
103 |     when the function is used.
104 |     """
105 |     @functools.wraps(func)
106 |     def new_func(*args, **kwargs):
107 | 
108 |         warnings.simplefilter('always', DeprecationWarning)  # turn off filter
109 |         warnings.warn("Call to deprecated function {}.".format(func.__name__),
110 |                       category=DeprecationWarning,
111 |                       stacklevel=2)
112 |         warnings.simplefilter('default', DeprecationWarning)  # reset filter
113 | 
114 |         return func(*args, **kwargs)
115 | 
116 |     return new_func
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | 142 |
143 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /docs/gedcom/element/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | gedcom.element API documentation 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |

Module gedcom.element

22 |
23 |
24 |

Module containing all relevant elements generated by a Parser. 25 | An element represents a line within GEDCOM data.

26 |
27 | 28 | Expand source code 29 | 30 |
# -*- coding: utf-8 -*-
 31 | 
 32 | # Python GEDCOM Parser
 33 | #
 34 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com)
 35 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com)
 36 | # Copyright (C) 2016 Andreas Oberritter
 37 | # Copyright (C) 2012 Madeleine Price Ball
 38 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu)
 39 | # Copyright (C) 2005 Brigham Young University
 40 | #
 41 | # This program is free software; you can redistribute it and/or modify
 42 | # it under the terms of the GNU General Public License as published by
 43 | # the Free Software Foundation; either version 2 of the License, or
 44 | # (at your option) any later version.
 45 | #
 46 | # This program is distributed in the hope that it will be useful,
 47 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
 48 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 49 | # GNU General Public License for more details.
 50 | #
 51 | # You should have received a copy of the GNU General Public License along
 52 | # with this program; if not, write to the Free Software Foundation, Inc.,
 53 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 54 | #
 55 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html
 56 | 
 57 | """
 58 | Module containing all relevant elements generated by a `gedcom.parser.Parser`.
 59 | An element represents a line within GEDCOM data.
 60 | """
 61 | 
 62 | __all__ = [
 63 |     "element",
 64 |     "family",
 65 |     "file",
 66 |     "individual",
 67 |     "object",
 68 |     "root"
 69 | ]
70 |
71 |
72 |
73 |

Sub-modules

74 |
75 |
gedcom.element.element
76 |
77 |

Base GEDCOM element

78 |
79 |
gedcom.element.family
80 |
81 |

GEDCOM element consisting of tag GEDCOM_TAG_FAMILY

82 |
83 |
gedcom.element.file
84 |
85 |

GEDCOM element consisting of tag GEDCOM_TAG_FILE

86 |
87 |
gedcom.element.individual
88 |
89 |

GEDCOM element consisting of tag GEDCOM_TAG_INDIVIDUAL

90 |
91 |
gedcom.element.object
92 |
93 |

GEDCOM element consisting of tag GEDCOM_TAG_OBJECT

94 |
95 |
gedcom.element.root
96 |
97 |

Virtual GEDCOM root element containing all logical records as children

98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | 131 |
132 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /gedcom/element/element.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Python GEDCOM Parser 4 | # 5 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com) 6 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com) 7 | # Copyright (C) 2016 Andreas Oberritter 8 | # Copyright (C) 2012 Madeleine Price Ball 9 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu) 10 | # Copyright (C) 2005 Brigham Young University 11 | # 12 | # This program is free software; you can redistribute it and/or modify 13 | # it under the terms of the GNU General Public License as published by 14 | # the Free Software Foundation; either version 2 of the License, or 15 | # (at your option) any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU General Public License along 23 | # with this program; if not, write to the Free Software Foundation, Inc., 24 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 25 | # 26 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html 27 | 28 | """ 29 | Base GEDCOM element 30 | """ 31 | 32 | from sys import version_info 33 | from gedcom.helpers import deprecated 34 | import gedcom.tags 35 | 36 | 37 | class Element(object): 38 | """GEDCOM element 39 | 40 | Each line in a GEDCOM file is an element with the format 41 | 42 | `level [pointer] tag [value]` 43 | 44 | where `level` and `tag` are required, and `pointer` and `value` are 45 | optional. Elements are arranged hierarchically according to their 46 | level, and elements with a level of zero are at the top level. 47 | Elements with a level greater than zero are children of their 48 | parent. 49 | 50 | A pointer has the format `@pname@`, where `pname` is any sequence of 51 | characters and numbers. The pointer identifies the object being 52 | pointed to, so that any pointer included as the value of any 53 | element points back to the original object. For example, an 54 | element may have a `FAMS` tag whose value is `@F1@`, meaning that this 55 | element points to the family record in which the associated person 56 | is a spouse. Likewise, an element with a tag of `FAMC` has a value 57 | that points to a family record in which the associated person is a 58 | child. 59 | 60 | See a GEDCOM file for examples of tags and their values. 61 | 62 | Tags available to an element are seen here: `gedcom.tags` 63 | """ 64 | 65 | def __init__(self, level, pointer, tag, value, crlf="\n", multi_line=True): 66 | # basic element info 67 | self.__level = level 68 | self.__pointer = pointer 69 | self.__tag = tag 70 | self.__value = value 71 | self.__crlf = crlf 72 | 73 | # structuring 74 | self.__children = [] 75 | self.__parent = None 76 | 77 | if multi_line: 78 | self.set_multi_line_value(value) 79 | 80 | def get_level(self): 81 | """Returns the level of this element from within the GEDCOM file 82 | :rtype: int 83 | """ 84 | return self.__level 85 | 86 | def get_pointer(self): 87 | """Returns the pointer of this element from within the GEDCOM file 88 | :rtype: str 89 | """ 90 | return self.__pointer 91 | 92 | def get_tag(self): 93 | """Returns the tag of this element from within the GEDCOM file 94 | :rtype: str 95 | """ 96 | return self.__tag 97 | 98 | def get_value(self): 99 | """Return the value of this element from within the GEDCOM file 100 | :rtype: str 101 | """ 102 | return self.__value 103 | 104 | def set_value(self, value): 105 | """Sets the value of this element 106 | :type value: str 107 | """ 108 | self.__value = value 109 | 110 | def get_multi_line_value(self): 111 | """Returns the value of this element including concatenations or continuations 112 | :rtype: str 113 | """ 114 | result = self.get_value() 115 | last_crlf = self.__crlf 116 | for element in self.get_child_elements(): 117 | tag = element.get_tag() 118 | if tag == gedcom.tags.GEDCOM_TAG_CONCATENATION: 119 | result += element.get_value() 120 | last_crlf = element.__crlf 121 | elif tag == gedcom.tags.GEDCOM_TAG_CONTINUED: 122 | result += last_crlf + element.get_value() 123 | last_crlf = element.__crlf 124 | return result 125 | 126 | def __available_characters(self): 127 | """Get the number of available characters of the elements original string 128 | :rtype: int 129 | """ 130 | element_characters = len(self.to_gedcom_string()) 131 | return 0 if element_characters > 255 else 255 - element_characters 132 | 133 | def __line_length(self, line): 134 | """@TODO Write docs. 135 | :type line: str 136 | :rtype: int 137 | """ 138 | total_characters = len(line) 139 | available_characters = self.__available_characters() 140 | if total_characters <= available_characters: 141 | return total_characters 142 | spaces = 0 143 | while spaces < available_characters and line[available_characters - spaces - 1] == ' ': 144 | spaces += 1 145 | if spaces == available_characters: 146 | return available_characters 147 | return available_characters - spaces 148 | 149 | def __set_bounded_value(self, value): 150 | """@TODO Write docs. 151 | :type value: str 152 | :rtype: int 153 | """ 154 | line_length = self.__line_length(value) 155 | self.set_value(value[:line_length]) 156 | return line_length 157 | 158 | def __add_bounded_child(self, tag, value): 159 | """@TODO Write docs. 160 | :type tag: str 161 | :type value: str 162 | :rtype: int 163 | """ 164 | child = self.new_child_element(tag) 165 | return child.__set_bounded_value(value) 166 | 167 | def __add_concatenation(self, string): 168 | """@TODO Write docs. 169 | :rtype: str 170 | """ 171 | index = 0 172 | size = len(string) 173 | while index < size: 174 | index += self.__add_bounded_child(gedcom.tags.GEDCOM_TAG_CONCATENATION, string[index:]) 175 | 176 | def set_multi_line_value(self, value): 177 | """Sets the value of this element, adding concatenation and continuation lines when necessary 178 | :type value: str 179 | """ 180 | self.set_value('') 181 | self.get_child_elements()[:] = [child for child in self.get_child_elements() if 182 | child.get_tag() not in (gedcom.tags.GEDCOM_TAG_CONCATENATION, gedcom.tags.GEDCOM_TAG_CONTINUED)] 183 | 184 | lines = value.splitlines() 185 | if lines: 186 | line = lines.pop(0) 187 | n = self.__set_bounded_value(line) 188 | self.__add_concatenation(line[n:]) 189 | 190 | for line in lines: 191 | n = self.__add_bounded_child(gedcom.tags.GEDCOM_TAG_CONTINUED, line) 192 | self.__add_concatenation(line[n:]) 193 | 194 | def get_child_elements(self): 195 | """Returns the direct child elements of this element 196 | :rtype: list of Element 197 | """ 198 | return self.__children 199 | 200 | def new_child_element(self, tag, pointer="", value=""): 201 | """Creates and returns a new child element of this element 202 | 203 | :type tag: str 204 | :type pointer: str 205 | :type value: str 206 | :rtype: Element 207 | """ 208 | from gedcom.element.family import FamilyElement 209 | from gedcom.element.file import FileElement 210 | from gedcom.element.individual import IndividualElement 211 | from gedcom.element.object import ObjectElement 212 | 213 | # Differentiate between the type of the new child element 214 | if tag == gedcom.tags.GEDCOM_TAG_FAMILY: 215 | child_element = FamilyElement(self.get_level() + 1, pointer, tag, value, self.__crlf) 216 | elif tag == gedcom.tags.GEDCOM_TAG_FILE: 217 | child_element = FileElement(self.get_level() + 1, pointer, tag, value, self.__crlf) 218 | elif tag == gedcom.tags.GEDCOM_TAG_INDIVIDUAL: 219 | child_element = IndividualElement(self.get_level() + 1, pointer, tag, value, self.__crlf) 220 | elif tag == gedcom.tags.GEDCOM_TAG_OBJECT: 221 | child_element = ObjectElement(self.get_level() + 1, pointer, tag, value, self.__crlf) 222 | else: 223 | child_element = Element(self.get_level() + 1, pointer, tag, value, self.__crlf) 224 | 225 | self.add_child_element(child_element) 226 | 227 | return child_element 228 | 229 | def add_child_element(self, element): 230 | """Adds a child element to this element 231 | 232 | :type element: Element 233 | """ 234 | self.get_child_elements().append(element) 235 | element.set_parent_element(self) 236 | 237 | return element 238 | 239 | def get_parent_element(self): 240 | """Returns the parent element of this element 241 | :rtype: Element 242 | """ 243 | return self.__parent 244 | 245 | def set_parent_element(self, element): 246 | """Adds a parent element to this element 247 | 248 | There's usually no need to call this method manually, 249 | `add_child_element()` calls it automatically. 250 | 251 | :type element: Element 252 | """ 253 | self.__parent = element 254 | 255 | @deprecated 256 | def get_individual(self): 257 | """Returns this element and all of its sub-elements represented as a GEDCOM string 258 | ::deprecated:: As of version 1.0.0 use `to_gedcom_string()` method instead 259 | :rtype: str 260 | """ 261 | return self.to_gedcom_string(True) 262 | 263 | def to_gedcom_string(self, recursive=False): 264 | """Formats this element and optionally all of its sub-elements into a GEDCOM string 265 | :type recursive: bool 266 | :rtype: str 267 | """ 268 | 269 | result = str(self.get_level()) 270 | 271 | if self.get_pointer() != "": 272 | result += ' ' + self.get_pointer() 273 | 274 | result += ' ' + self.get_tag() 275 | 276 | if self.get_value() != "": 277 | result += ' ' + self.get_value() 278 | 279 | result += self.__crlf 280 | 281 | if self.get_level() < 0: 282 | result = '' 283 | 284 | if recursive: 285 | for child_element in self.get_child_elements(): 286 | result += child_element.to_gedcom_string(True) 287 | 288 | return result 289 | 290 | def __str__(self): 291 | """:rtype: str""" 292 | if version_info[0] >= 3: 293 | return self.to_gedcom_string() 294 | 295 | return self.to_gedcom_string().encode('utf-8-sig') 296 | -------------------------------------------------------------------------------- /docs/gedcom/element/root.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | gedcom.element.root API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

Module gedcom.element.root

21 |
22 |
23 |

Virtual GEDCOM root element containing all logical records as children

24 |
25 | 26 | Expand source code 27 | 28 |
# -*- coding: utf-8 -*-
 29 | 
 30 | # Python GEDCOM Parser
 31 | #
 32 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com)
 33 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com)
 34 | # Copyright (C) 2016 Andreas Oberritter
 35 | # Copyright (C) 2012 Madeleine Price Ball
 36 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu)
 37 | # Copyright (C) 2005 Brigham Young University
 38 | #
 39 | # This program is free software; you can redistribute it and/or modify
 40 | # it under the terms of the GNU General Public License as published by
 41 | # the Free Software Foundation; either version 2 of the License, or
 42 | # (at your option) any later version.
 43 | #
 44 | # This program is distributed in the hope that it will be useful,
 45 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
 46 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 47 | # GNU General Public License for more details.
 48 | #
 49 | # You should have received a copy of the GNU General Public License along
 50 | # with this program; if not, write to the Free Software Foundation, Inc.,
 51 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 52 | #
 53 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html
 54 | 
 55 | """Virtual GEDCOM root element containing all logical records as children"""
 56 | 
 57 | from gedcom.element.element import Element
 58 | 
 59 | 
 60 | class RootElement(Element):
 61 |     """Virtual GEDCOM root element containing all logical records as children"""
 62 | 
 63 |     def __init__(self, level=-1, pointer="", tag="ROOT", value="", crlf="\n", multi_line=True):
 64 |         super(RootElement, self).__init__(level, pointer, tag, value, crlf, multi_line)
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |

Classes

75 |
76 |
77 | class RootElement 78 | (level=-1, pointer='', tag='ROOT', value='', crlf='\n', multi_line=True) 79 |
80 |
81 |

Virtual GEDCOM root element containing all logical records as children

82 |
83 | 84 | Expand source code 85 | 86 |
class RootElement(Element):
 87 |     """Virtual GEDCOM root element containing all logical records as children"""
 88 | 
 89 |     def __init__(self, level=-1, pointer="", tag="ROOT", value="", crlf="\n", multi_line=True):
 90 |         super(RootElement, self).__init__(level, pointer, tag, value, crlf, multi_line)
91 |
92 |

Ancestors

93 | 96 |

Inherited members

97 | 117 |
118 |
119 |
120 |
121 | 141 |
142 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /docs/gedcom/element/file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | gedcom.element.file API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

Module gedcom.element.file

21 |
22 |
23 |

GEDCOM element consisting of tag GEDCOM_TAG_FILE

24 |
25 | 26 | Expand source code 27 | 28 |
# -*- coding: utf-8 -*-
 29 | 
 30 | # Python GEDCOM Parser
 31 | #
 32 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com)
 33 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com)
 34 | # Copyright (C) 2016 Andreas Oberritter
 35 | # Copyright (C) 2012 Madeleine Price Ball
 36 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu)
 37 | # Copyright (C) 2005 Brigham Young University
 38 | #
 39 | # This program is free software; you can redistribute it and/or modify
 40 | # it under the terms of the GNU General Public License as published by
 41 | # the Free Software Foundation; either version 2 of the License, or
 42 | # (at your option) any later version.
 43 | #
 44 | # This program is distributed in the hope that it will be useful,
 45 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
 46 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 47 | # GNU General Public License for more details.
 48 | #
 49 | # You should have received a copy of the GNU General Public License along
 50 | # with this program; if not, write to the Free Software Foundation, Inc.,
 51 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 52 | #
 53 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html
 54 | 
 55 | """GEDCOM element consisting of tag `gedcom.tags.GEDCOM_TAG_FILE`"""
 56 | 
 57 | from gedcom.element.element import Element
 58 | import gedcom.tags
 59 | 
 60 | 
 61 | class NotAnActualFileError(Exception):
 62 |     pass
 63 | 
 64 | 
 65 | class FileElement(Element):
 66 | 
 67 |     def get_tag(self):
 68 |         return gedcom.tags.GEDCOM_TAG_FILE
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |

Classes

79 |
80 |
81 | class FileElement 82 | (level, pointer, tag, value, crlf='\n', multi_line=True) 83 |
84 |
85 |

GEDCOM element

86 |

Each line in a GEDCOM file is an element with the format

87 |

level [pointer] tag [value]

88 |

where level and tag are required, and pointer and value are 89 | optional. 90 | Elements are arranged hierarchically according to their 91 | level, and elements with a level of zero are at the top level. 92 | Elements with a level greater than zero are children of their 93 | parent.

94 |

A pointer has the format @pname@, where pname is any sequence of 95 | characters and numbers. The pointer identifies the object being 96 | pointed to, so that any pointer included as the value of any 97 | element points back to the original object. 98 | For example, an 99 | element may have a FAMS tag whose value is @F1@, meaning that this 100 | element points to the family record in which the associated person 101 | is a spouse. Likewise, an element with a tag of FAMC has a value 102 | that points to a family record in which the associated person is a 103 | child.

104 |

See a GEDCOM file for examples of tags and their values.

105 |

Tags available to an element are seen here: gedcom.tags

106 |
107 | 108 | Expand source code 109 | 110 |
class FileElement(Element):
111 | 
112 |     def get_tag(self):
113 |         return gedcom.tags.GEDCOM_TAG_FILE
114 |
115 |

Ancestors

116 | 119 |

Inherited members

120 | 140 |
141 |
142 | class NotAnActualFileError 143 | (...) 144 |
145 |
146 |

Common base class for all non-exit exceptions.

147 |
148 | 149 | Expand source code 150 | 151 |
class NotAnActualFileError(Exception):
152 |     pass
153 |
154 |

Ancestors

155 |
    156 |
  • builtins.Exception
  • 157 |
  • builtins.BaseException
  • 158 |
159 |
160 |
161 |
162 |
163 | 186 |
187 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /docs/gedcom/element/family.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | gedcom.element.family API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

Module gedcom.element.family

21 |
22 |
23 |

GEDCOM element consisting of tag GEDCOM_TAG_FAMILY

24 |
25 | 26 | Expand source code 27 | 28 |
# -*- coding: utf-8 -*-
 29 | 
 30 | # Python GEDCOM Parser
 31 | #
 32 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com)
 33 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com)
 34 | # Copyright (C) 2016 Andreas Oberritter
 35 | # Copyright (C) 2012 Madeleine Price Ball
 36 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu)
 37 | # Copyright (C) 2005 Brigham Young University
 38 | #
 39 | # This program is free software; you can redistribute it and/or modify
 40 | # it under the terms of the GNU General Public License as published by
 41 | # the Free Software Foundation; either version 2 of the License, or
 42 | # (at your option) any later version.
 43 | #
 44 | # This program is distributed in the hope that it will be useful,
 45 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
 46 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 47 | # GNU General Public License for more details.
 48 | #
 49 | # You should have received a copy of the GNU General Public License along
 50 | # with this program; if not, write to the Free Software Foundation, Inc.,
 51 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 52 | #
 53 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html
 54 | 
 55 | """GEDCOM element consisting of tag `gedcom.tags.GEDCOM_TAG_FAMILY`"""
 56 | 
 57 | from gedcom.element.element import Element
 58 | import gedcom.tags
 59 | 
 60 | 
 61 | class NotAnActualFamilyError(Exception):
 62 |     pass
 63 | 
 64 | 
 65 | class FamilyElement(Element):
 66 | 
 67 |     def get_tag(self):
 68 |         return gedcom.tags.GEDCOM_TAG_FAMILY
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |

Classes

79 |
80 |
81 | class FamilyElement 82 | (level, pointer, tag, value, crlf='\n', multi_line=True) 83 |
84 |
85 |

GEDCOM element

86 |

Each line in a GEDCOM file is an element with the format

87 |

level [pointer] tag [value]

88 |

where level and tag are required, and pointer and value are 89 | optional. 90 | Elements are arranged hierarchically according to their 91 | level, and elements with a level of zero are at the top level. 92 | Elements with a level greater than zero are children of their 93 | parent.

94 |

A pointer has the format @pname@, where pname is any sequence of 95 | characters and numbers. The pointer identifies the object being 96 | pointed to, so that any pointer included as the value of any 97 | element points back to the original object. 98 | For example, an 99 | element may have a FAMS tag whose value is @F1@, meaning that this 100 | element points to the family record in which the associated person 101 | is a spouse. Likewise, an element with a tag of FAMC has a value 102 | that points to a family record in which the associated person is a 103 | child.

104 |

See a GEDCOM file for examples of tags and their values.

105 |

Tags available to an element are seen here: gedcom.tags

106 |
107 | 108 | Expand source code 109 | 110 |
class FamilyElement(Element):
111 | 
112 |     def get_tag(self):
113 |         return gedcom.tags.GEDCOM_TAG_FAMILY
114 |
115 |

Ancestors

116 | 119 |

Inherited members

120 | 140 |
141 |
142 | class NotAnActualFamilyError 143 | (...) 144 |
145 |
146 |

Common base class for all non-exit exceptions.

147 |
148 | 149 | Expand source code 150 | 151 |
class NotAnActualFamilyError(Exception):
152 |     pass
153 |
154 |

Ancestors

155 |
    156 |
  • builtins.Exception
  • 157 |
  • builtins.BaseException
  • 158 |
159 |
160 |
161 |
162 |
163 | 186 |
187 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "ff4ffecf922d35b96c175b7a2a6e76ba732f698d575beb753c2742046c2da00d" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.5" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": {}, 19 | "develop": { 20 | "appdirs": { 21 | "hashes": [ 22 | "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", 23 | "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" 24 | ], 25 | "version": "==1.4.3" 26 | }, 27 | "bleach": { 28 | "hashes": [ 29 | "sha256:53165a6596e7899c4338d847315fec508110a53bd6fd15c127c2e0d0860264e3", 30 | "sha256:f8dfd8a7e26443e986c4e44df31870da8e906ea61096af06ba5d5cc2d519842a" 31 | ], 32 | "version": "==3.1.3" 33 | }, 34 | "certifi": { 35 | "hashes": [ 36 | "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", 37 | "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" 38 | ], 39 | "version": "==2019.11.28" 40 | }, 41 | "chardet": { 42 | "hashes": [ 43 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 44 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 45 | ], 46 | "version": "==3.0.4" 47 | }, 48 | "distlib": { 49 | "hashes": [ 50 | "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21" 51 | ], 52 | "version": "==0.3.0" 53 | }, 54 | "docutils": { 55 | "hashes": [ 56 | "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", 57 | "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" 58 | ], 59 | "version": "==0.16" 60 | }, 61 | "filelock": { 62 | "hashes": [ 63 | "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", 64 | "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" 65 | ], 66 | "version": "==3.0.12" 67 | }, 68 | "idna": { 69 | "hashes": [ 70 | "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", 71 | "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" 72 | ], 73 | "version": "==2.9" 74 | }, 75 | "importlib-metadata": { 76 | "hashes": [ 77 | "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302", 78 | "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b" 79 | ], 80 | "markers": "python_version < '3.8'", 81 | "version": "==1.5.0" 82 | }, 83 | "importlib-resources": { 84 | "hashes": [ 85 | "sha256:4019b6a9082d8ada9def02bece4a76b131518866790d58fdda0b5f8c603b36c2", 86 | "sha256:dd98ceeef3f5ad2ef4cc287b8586da4ebad15877f351e9688987ad663a0a29b8" 87 | ], 88 | "markers": "python_version < '3.7'", 89 | "version": "==1.4.0" 90 | }, 91 | "mako": { 92 | "hashes": [ 93 | "sha256:3139c5d64aa5d175dbafb95027057128b5fbd05a40c53999f3905ceb53366d9d", 94 | "sha256:8e8b53c71c7e59f3de716b6832c4e401d903af574f6962edbbbf6ecc2a5fe6c9" 95 | ], 96 | "version": "==1.1.2" 97 | }, 98 | "markdown": { 99 | "hashes": [ 100 | "sha256:90fee683eeabe1a92e149f7ba74e5ccdc81cd397bd6c516d93a8da0ef90b6902", 101 | "sha256:e4795399163109457d4c5af2183fbe6b60326c17cfdf25ce6e7474c6624f725d" 102 | ], 103 | "version": "==3.2.1" 104 | }, 105 | "markupsafe": { 106 | "hashes": [ 107 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 108 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 109 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 110 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 111 | "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", 112 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 113 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 114 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 115 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 116 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 117 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 118 | "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", 119 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 120 | "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", 121 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 122 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 123 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 124 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 125 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 126 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 127 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 128 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 129 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 130 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 131 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 132 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 133 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 134 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 135 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 136 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 137 | "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", 138 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", 139 | "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" 140 | ], 141 | "version": "==1.1.1" 142 | }, 143 | "packaging": { 144 | "hashes": [ 145 | "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", 146 | "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" 147 | ], 148 | "version": "==20.3" 149 | }, 150 | "pdoc3": { 151 | "hashes": [ 152 | "sha256:ebca75b7fcf23f3b4320abe23339834d3f08c28517718e9d29e555fc38eeb33c" 153 | ], 154 | "index": "pypi", 155 | "version": "==0.7.5" 156 | }, 157 | "pkginfo": { 158 | "hashes": [ 159 | "sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb", 160 | "sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32" 161 | ], 162 | "version": "==1.5.0.1" 163 | }, 164 | "pluggy": { 165 | "hashes": [ 166 | "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", 167 | "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" 168 | ], 169 | "version": "==0.13.1" 170 | }, 171 | "py": { 172 | "hashes": [ 173 | "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", 174 | "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" 175 | ], 176 | "version": "==1.8.1" 177 | }, 178 | "pygments": { 179 | "hashes": [ 180 | "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44", 181 | "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324" 182 | ], 183 | "version": "==2.6.1" 184 | }, 185 | "pyparsing": { 186 | "hashes": [ 187 | "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", 188 | "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" 189 | ], 190 | "version": "==2.4.6" 191 | }, 192 | "readme-renderer": { 193 | "hashes": [ 194 | "sha256:1b6d8dd1673a0b293766b4106af766b6eff3654605f9c4f239e65de6076bc222", 195 | "sha256:e67d64242f0174a63c3b727801a2fff4c1f38ebe5d71d95ff7ece081945a6cd4" 196 | ], 197 | "version": "==25.0" 198 | }, 199 | "requests": { 200 | "hashes": [ 201 | "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", 202 | "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" 203 | ], 204 | "version": "==2.23.0" 205 | }, 206 | "requests-toolbelt": { 207 | "hashes": [ 208 | "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f", 209 | "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0" 210 | ], 211 | "version": "==0.9.1" 212 | }, 213 | "six": { 214 | "hashes": [ 215 | "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", 216 | "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" 217 | ], 218 | "version": "==1.14.0" 219 | }, 220 | "toml": { 221 | "hashes": [ 222 | "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", 223 | "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" 224 | ], 225 | "version": "==0.10.0" 226 | }, 227 | "tox": { 228 | "hashes": [ 229 | "sha256:0cbe98369081fa16bd6f1163d3d0b2a62afa29d402ccfad2bd09fb2668be0956", 230 | "sha256:676f1e3e7de245ad870f956436b84ea226210587d1f72c8dfb8cd5ac7b6f0e70" 231 | ], 232 | "index": "pypi", 233 | "version": "==3.14.5" 234 | }, 235 | "tqdm": { 236 | "hashes": [ 237 | "sha256:0d8b5afb66e23d80433102e9bd8b5c8b65d34c2a2255b2de58d97bd2ea8170fd", 238 | "sha256:f35fb121bafa030bd94e74fcfd44f3c2830039a2ddef7fc87ef1c2d205237b24" 239 | ], 240 | "version": "==4.43.0" 241 | }, 242 | "twine": { 243 | "hashes": [ 244 | "sha256:630fadd6e342e725930be6c696537e3f9ccc54331742b16245dab292a17d0460", 245 | "sha256:a3d22aab467b4682a22de4a422632e79d07eebd07ff2a7079effb13f8a693787" 246 | ], 247 | "index": "pypi", 248 | "version": "==1.15.0" 249 | }, 250 | "urllib3": { 251 | "hashes": [ 252 | "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", 253 | "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" 254 | ], 255 | "version": "==1.25.8" 256 | }, 257 | "virtualenv": { 258 | "hashes": [ 259 | "sha256:87831f1070534b636fea2241dd66f3afe37ac9041bcca6d0af3215cdcfbf7d82", 260 | "sha256:f3128d882383c503003130389bf892856341c1da12c881ae24d6358c82561b55" 261 | ], 262 | "version": "==20.0.13" 263 | }, 264 | "webencodings": { 265 | "hashes": [ 266 | "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", 267 | "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" 268 | ], 269 | "version": "==0.5.1" 270 | }, 271 | "wheel": { 272 | "hashes": [ 273 | "sha256:8788e9155fe14f54164c1b9eb0a319d98ef02c160725587ad60f14ddc57b6f96", 274 | "sha256:df277cb51e61359aba502208d680f90c0493adec6f0e848af94948778aed386e" 275 | ], 276 | "index": "pypi", 277 | "version": "==0.34.2" 278 | }, 279 | "zipp": { 280 | "hashes": [ 281 | "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1", 282 | "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921" 283 | ], 284 | "markers": "python_version < '3.8'", 285 | "version": "==1.2.0" 286 | } 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /docs/gedcom/element/object.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | gedcom.element.object API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

Module gedcom.element.object

21 |
22 |
23 |

GEDCOM element consisting of tag GEDCOM_TAG_OBJECT

24 |
25 | 26 | Expand source code 27 | 28 |
# -*- coding: utf-8 -*-
 29 | 
 30 | # Python GEDCOM Parser
 31 | #
 32 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com)
 33 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com)
 34 | # Copyright (C) 2016 Andreas Oberritter
 35 | # Copyright (C) 2012 Madeleine Price Ball
 36 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu)
 37 | # Copyright (C) 2005 Brigham Young University
 38 | #
 39 | # This program is free software; you can redistribute it and/or modify
 40 | # it under the terms of the GNU General Public License as published by
 41 | # the Free Software Foundation; either version 2 of the License, or
 42 | # (at your option) any later version.
 43 | #
 44 | # This program is distributed in the hope that it will be useful,
 45 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
 46 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 47 | # GNU General Public License for more details.
 48 | #
 49 | # You should have received a copy of the GNU General Public License along
 50 | # with this program; if not, write to the Free Software Foundation, Inc.,
 51 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 52 | #
 53 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html
 54 | 
 55 | """GEDCOM element consisting of tag `gedcom.tags.GEDCOM_TAG_OBJECT`"""
 56 | 
 57 | from gedcom.element.element import Element
 58 | import gedcom.tags
 59 | 
 60 | 
 61 | class NotAnActualObjectError(Exception):
 62 |     pass
 63 | 
 64 | 
 65 | class ObjectElement(Element):
 66 | 
 67 |     def is_object(self):
 68 |         """Checks if this element is an actual object
 69 |         :rtype: bool
 70 |         """
 71 |         return self.get_tag() == gedcom.tags.GEDCOM_TAG_OBJECT
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |

Classes

82 |
83 |
84 | class NotAnActualObjectError 85 | (...) 86 |
87 |
88 |

Common base class for all non-exit exceptions.

89 |
90 | 91 | Expand source code 92 | 93 |
class NotAnActualObjectError(Exception):
 94 |     pass
95 |
96 |

Ancestors

97 |
    98 |
  • builtins.Exception
  • 99 |
  • builtins.BaseException
  • 100 |
101 |
102 |
103 | class ObjectElement 104 | (level, pointer, tag, value, crlf='\n', multi_line=True) 105 |
106 |
107 |

GEDCOM element

108 |

Each line in a GEDCOM file is an element with the format

109 |

level [pointer] tag [value]

110 |

where level and tag are required, and pointer and value are 111 | optional. 112 | Elements are arranged hierarchically according to their 113 | level, and elements with a level of zero are at the top level. 114 | Elements with a level greater than zero are children of their 115 | parent.

116 |

A pointer has the format @pname@, where pname is any sequence of 117 | characters and numbers. The pointer identifies the object being 118 | pointed to, so that any pointer included as the value of any 119 | element points back to the original object. 120 | For example, an 121 | element may have a FAMS tag whose value is @F1@, meaning that this 122 | element points to the family record in which the associated person 123 | is a spouse. Likewise, an element with a tag of FAMC has a value 124 | that points to a family record in which the associated person is a 125 | child.

126 |

See a GEDCOM file for examples of tags and their values.

127 |

Tags available to an element are seen here: gedcom.tags

128 |
129 | 130 | Expand source code 131 | 132 |
class ObjectElement(Element):
133 | 
134 |     def is_object(self):
135 |         """Checks if this element is an actual object
136 |         :rtype: bool
137 |         """
138 |         return self.get_tag() == gedcom.tags.GEDCOM_TAG_OBJECT
139 |
140 |

Ancestors

141 | 144 |

Methods

145 |
146 |
147 | def is_object(self) 148 |
149 |
150 |

Checks if this element is an actual object 151 | :rtype: bool

152 |
153 | 154 | Expand source code 155 | 156 |
def is_object(self):
157 |     """Checks if this element is an actual object
158 |     :rtype: bool
159 |     """
160 |     return self.get_tag() == gedcom.tags.GEDCOM_TAG_OBJECT
161 |
162 |
163 |
164 |

Inherited members

165 | 185 |
186 |
187 |
188 |
189 | 215 |
216 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /docs/gedcom/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | gedcom API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

Module gedcom

21 |
22 |
23 |

A Python module for parsing, analyzing, and manipulating GEDCOM files.

24 |

Installation

25 |

The module can be installed via pipenv or simply pip.

26 |

Run pip3 install python-gedcom to install or pip3 install python-gedcom --upgrade 27 | to upgrade to the newest version uploaded to the PyPI repository.

28 |
29 |

Tip

30 |

Using pipenv simplifies the installation and maintenance of dependencies.

31 |
32 |

Pre-releases

33 |

If you want to use the latest pre-release of the python-gedcom package, 34 | simply append the --pre option to pip3: pip3 install python-gedcom --pre

35 |

Important classes and modules

36 | 41 |

Example usage

42 |

When successfully installed you may import the gedcom package and use it like so:

43 |
from gedcom.element.individual import IndividualElement
 44 | from gedcom.parser import Parser
 45 | 
 46 | # Path to your ".ged" file
 47 | file_path = ''
 48 | 
 49 | # Initialize the parser
 50 | gedcom_parser = Parser()
 51 | 
 52 | # Parse your file
 53 | gedcom_parser.parse_file(file_path)
 54 | 
 55 | root_child_elements = gedcom_parser.get_root_child_elements()
 56 | 
 57 | # Iterate through all root child elements
 58 | for element in root_child_elements:
 59 | 
 60 |     # Is the "element" an actual "IndividualElement"? (Allows usage of extra functions such as "surname_match" and "get_name".)
 61 |     if isinstance(element, IndividualElement):
 62 | 
 63 |         # Get all individuals whose surname matches "Doe"
 64 |         if element.surname_match('Doe'):
 65 | 
 66 |             # Unpack the name tuple
 67 |             (first, last) = element.get_name()
 68 | 
 69 |             # Print the first and last name of the found individual
 70 |             print(first + " " + last)
 71 | 
72 |
73 |

Tip

74 |

Please have a look at the test files found in the 75 | tests/ directory in the source code on GitHub.

76 |
77 |

Strict parsing

78 |

Large sites like Ancestry and MyHeritage (among others) don't always produce perfectly formatted GEDCOM files. 79 | If you encounter errors in parsing, you might consider disabling strict parsing which is enabled by default:

80 |
from gedcom.parser import Parser
 81 | 
 82 | file_path = '' # Path to your `.ged` file
 83 | 
 84 | gedcom_parser = Parser()
 85 | gedcom_parser.parse_file(file_path, False) # Disable strict parsing
 86 | 
87 |

Disabling strict parsing will allow the parser to gracefully handle the following quirks:

88 |
    89 |
  • Multi-line fields that don't use CONC or CONT
  • 90 |
  • Handle the last line not ending in a CRLF (\r\n)
  • 91 |
92 |

License

93 |

Licensed under the GNU General Public License v2

94 |

Python GEDCOM Parser 95 |
Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com) 96 |
Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com) 97 |
Copyright (C) 2016 Andreas Oberritter 98 |
Copyright (C) 2012 Madeleine Price Ball 99 |
Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu) 100 |
Copyright (C) 2005 Brigham Young University

101 |

This program is free software; you can redistribute it and/or modify 102 | it under the terms of the GNU General Public License as published by 103 | the Free Software Foundation; either version 2 of the License, or 104 | (at your option) any later version.

105 |

This program is distributed in the hope that it will be useful, 106 | but WITHOUT ANY WARRANTY; without even the implied warranty of 107 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 108 | See the 109 | GNU General Public License for more details.

110 |

You should have received a copy of the GNU General Public License along 111 | with this program; if not, write to the Free Software Foundation, Inc., 112 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

113 |
114 | 115 | Expand source code 116 | 117 |
# -*- coding: utf-8 -*-
118 | 
119 | # Python GEDCOM Parser
120 | #
121 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com)
122 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com)
123 | # Copyright (C) 2016 Andreas Oberritter
124 | # Copyright (C) 2012 Madeleine Price Ball
125 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu)
126 | # Copyright (C) 2005 Brigham Young University
127 | #
128 | # This program is free software; you can redistribute it and/or modify
129 | # it under the terms of the GNU General Public License as published by
130 | # the Free Software Foundation; either version 2 of the License, or
131 | # (at your option) any later version.
132 | #
133 | # This program is distributed in the hope that it will be useful,
134 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
135 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
136 | # GNU General Public License for more details.
137 | #
138 | # You should have received a copy of the GNU General Public License along
139 | # with this program; if not, write to the Free Software Foundation, Inc.,
140 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
141 | #
142 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html
143 | 
144 | """
145 | A Python module for parsing, analyzing, and manipulating GEDCOM files.
146 | 
147 | .. include:: ./gedcom.md
148 | """
149 | 
150 | __all__ = [
151 |     # Subpackages
152 |     "element",
153 |     # Modules
154 |     "helpers",
155 |     "parser",
156 |     "tags"
157 | ]
158 |
159 |
160 |
161 |

Sub-modules

162 |
163 |
gedcom.element
164 |
165 |

Module containing all relevant elements generated by a Parser. 166 | An element represents a line within GEDCOM data.

167 |
168 |
gedcom.helpers
169 |
170 |

Helper methods.

171 |
172 |
gedcom.parser
173 |
174 |

Module containing the actual Parser used to generate elements - out of each line - 175 | which can in return be manipulated.

176 |
177 |
gedcom.tags
178 |
179 |

GEDCOM tags.

180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 | 215 |
216 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /gedcom/element/individual.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Python GEDCOM Parser 4 | # 5 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com) 6 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com) 7 | # Copyright (C) 2016 Andreas Oberritter 8 | # Copyright (C) 2012 Madeleine Price Ball 9 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu) 10 | # Copyright (C) 2005 Brigham Young University 11 | # 12 | # This program is free software; you can redistribute it and/or modify 13 | # it under the terms of the GNU General Public License as published by 14 | # the Free Software Foundation; either version 2 of the License, or 15 | # (at your option) any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU General Public License along 23 | # with this program; if not, write to the Free Software Foundation, Inc., 24 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 25 | # 26 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html 27 | 28 | """GEDCOM element consisting of tag `gedcom.tags.GEDCOM_TAG_INDIVIDUAL`""" 29 | 30 | import re as regex 31 | from gedcom.element.element import Element 32 | from gedcom.helpers import deprecated 33 | import gedcom.tags 34 | 35 | 36 | class NotAnActualIndividualError(Exception): 37 | pass 38 | 39 | 40 | class IndividualElement(Element): 41 | 42 | def get_tag(self): 43 | return gedcom.tags.GEDCOM_TAG_INDIVIDUAL 44 | 45 | def is_deceased(self): 46 | """Checks if this individual is deceased 47 | :rtype: bool 48 | """ 49 | for child in self.get_child_elements(): 50 | if child.get_tag() == gedcom.tags.GEDCOM_TAG_DEATH: 51 | return True 52 | 53 | return False 54 | 55 | def is_child(self): 56 | """Checks if this element is a child of a family 57 | :rtype: bool 58 | """ 59 | found_child = False 60 | 61 | for child in self.get_child_elements(): 62 | if child.get_tag() == gedcom.tags.GEDCOM_TAG_FAMILY_CHILD: 63 | found_child = True 64 | 65 | return found_child 66 | 67 | def is_private(self): 68 | """Checks if this individual is marked private 69 | :rtype: bool 70 | """ 71 | for child in self.get_child_elements(): 72 | if child.get_tag() == gedcom.tags.GEDCOM_TAG_PRIVATE: 73 | private = child.get_value() 74 | if private == 'Y': 75 | return True 76 | 77 | return False 78 | 79 | def get_name(self): 80 | """Returns an individual's names as a tuple: (`str` given_name, `str` surname) 81 | :rtype: tuple 82 | """ 83 | given_name = "" 84 | surname = "" 85 | 86 | # Return the first gedcom.tags.GEDCOM_TAG_NAME that is found. 87 | # Alternatively as soon as we have both the gedcom.tags.GEDCOM_TAG_GIVEN_NAME and _SURNAME return those. 88 | found_given_name = False 89 | found_surname_name = False 90 | 91 | for child in self.get_child_elements(): 92 | if child.get_tag() == gedcom.tags.GEDCOM_TAG_NAME: 93 | # Some GEDCOM files don't use child tags but instead 94 | # place the name in the value of the NAME tag. 95 | if child.get_value() != "": 96 | name = child.get_value().split('/') 97 | 98 | if len(name) > 0: 99 | given_name = name[0].strip() 100 | if len(name) > 1: 101 | surname = name[1].strip() 102 | 103 | return given_name, surname 104 | 105 | for childOfChild in child.get_child_elements(): 106 | 107 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_GIVEN_NAME: 108 | given_name = childOfChild.get_value() 109 | found_given_name = True 110 | 111 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_SURNAME: 112 | surname = childOfChild.get_value() 113 | found_surname_name = True 114 | 115 | if found_given_name and found_surname_name: 116 | return given_name, surname 117 | 118 | # If we reach here we are probably returning empty strings 119 | return given_name, surname 120 | 121 | def get_all_names(self): 122 | return [a.get_value() for a in self.get_child_elements() if a.get_tag() == gedcom.tags.GEDCOM_TAG_NAME] 123 | 124 | def surname_match(self, surname_to_match): 125 | """Matches a string with the surname of an individual 126 | :type surname_to_match: str 127 | :rtype: bool 128 | """ 129 | (given_name, surname) = self.get_name() 130 | return regex.search(surname_to_match, surname, regex.IGNORECASE) 131 | 132 | @deprecated 133 | def given_match(self, name): 134 | """Matches a string with the given name of an individual 135 | ::deprecated:: As of version 1.0.0 use `given_name_match()` method instead 136 | :type name: str 137 | :rtype: bool 138 | """ 139 | return self.given_name_match(name) 140 | 141 | def given_name_match(self, given_name_to_match): 142 | """Matches a string with the given name of an individual 143 | :type given_name_to_match: str 144 | :rtype: bool 145 | """ 146 | (given_name, surname) = self.get_name() 147 | return regex.search(given_name_to_match, given_name, regex.IGNORECASE) 148 | 149 | def get_gender(self): 150 | """Returns the gender of a person in string format 151 | :rtype: str 152 | """ 153 | gender = "" 154 | 155 | for child in self.get_child_elements(): 156 | if child.get_tag() == gedcom.tags.GEDCOM_TAG_SEX: 157 | gender = child.get_value() 158 | 159 | return gender 160 | 161 | def get_birth_data(self): 162 | """Returns the birth data of a person formatted as a tuple: (`str` date, `str` place, `list` sources) 163 | :rtype: tuple 164 | """ 165 | date = "" 166 | place = "" 167 | sources = [] 168 | 169 | for child in self.get_child_elements(): 170 | if child.get_tag() == gedcom.tags.GEDCOM_TAG_BIRTH: 171 | for childOfChild in child.get_child_elements(): 172 | 173 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_DATE: 174 | date = childOfChild.get_value() 175 | 176 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_PLACE: 177 | place = childOfChild.get_value() 178 | 179 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_SOURCE: 180 | sources.append(childOfChild.get_value()) 181 | 182 | return date, place, sources 183 | 184 | def get_birth_year(self): 185 | """Returns the birth year of a person in integer format 186 | :rtype: int 187 | """ 188 | date = "" 189 | 190 | for child in self.get_child_elements(): 191 | if child.get_tag() == gedcom.tags.GEDCOM_TAG_BIRTH: 192 | for childOfChild in child.get_child_elements(): 193 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_DATE: 194 | date_split = childOfChild.get_value().split() 195 | date = date_split[len(date_split) - 1] 196 | 197 | if date == "": 198 | return -1 199 | try: 200 | return int(date) 201 | except ValueError: 202 | return -1 203 | 204 | def get_death_data(self): 205 | """Returns the death data of a person formatted as a tuple: (`str` date, `str` place, `list` sources) 206 | :rtype: tuple 207 | """ 208 | date = "" 209 | place = "" 210 | sources = [] 211 | 212 | for child in self.get_child_elements(): 213 | if child.get_tag() == gedcom.tags.GEDCOM_TAG_DEATH: 214 | for childOfChild in child.get_child_elements(): 215 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_DATE: 216 | date = childOfChild.get_value() 217 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_PLACE: 218 | place = childOfChild.get_value() 219 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_SOURCE: 220 | sources.append(childOfChild.get_value()) 221 | 222 | return date, place, sources 223 | 224 | def get_death_year(self): 225 | """Returns the death year of a person in integer format 226 | :rtype: int 227 | """ 228 | date = "" 229 | 230 | for child in self.get_child_elements(): 231 | if child.get_tag() == gedcom.tags.GEDCOM_TAG_DEATH: 232 | for childOfChild in child.get_child_elements(): 233 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_DATE: 234 | date_split = childOfChild.get_value().split() 235 | date = date_split[len(date_split) - 1] 236 | 237 | if date == "": 238 | return -1 239 | try: 240 | return int(date) 241 | except ValueError: 242 | return -1 243 | 244 | @deprecated 245 | def get_burial(self): 246 | """Returns the burial data of a person formatted as a tuple: (`str` date, `str´ place, `list` sources) 247 | ::deprecated:: As of version 1.0.0 use `get_burial_data()` method instead 248 | :rtype: tuple 249 | """ 250 | self.get_burial_data() 251 | 252 | def get_burial_data(self): 253 | """Returns the burial data of a person formatted as a tuple: (`str` date, `str´ place, `list` sources) 254 | :rtype: tuple 255 | """ 256 | date = "" 257 | place = "" 258 | sources = [] 259 | 260 | for child in self.get_child_elements(): 261 | if child.get_tag() == gedcom.tags.GEDCOM_TAG_BURIAL: 262 | for childOfChild in child.get_child_elements(): 263 | 264 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_DATE: 265 | date = childOfChild.get_value() 266 | 267 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_PLACE: 268 | place = childOfChild.get_value() 269 | 270 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_SOURCE: 271 | sources.append(childOfChild.get_value()) 272 | 273 | return date, place, sources 274 | 275 | @deprecated 276 | def get_census(self): 277 | """Returns a list of censuses of an individual formatted as tuples: (`str` date, `str´ place, `list` sources) 278 | ::deprecated:: As of version 1.0.0 use `get_census_data()` method instead 279 | :rtype: list of tuple 280 | """ 281 | self.get_census_data() 282 | 283 | def get_census_data(self): 284 | """Returns a list of censuses of an individual formatted as tuples: (`str` date, `str´ place, `list` sources) 285 | :rtype: list of tuple 286 | """ 287 | census = [] 288 | 289 | for child in self.get_child_elements(): 290 | if child.get_tag() == gedcom.tags.GEDCOM_TAG_CENSUS: 291 | 292 | date = '' 293 | place = '' 294 | sources = [] 295 | 296 | for childOfChild in child.get_child_elements(): 297 | 298 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_DATE: 299 | date = childOfChild.get_value() 300 | 301 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_PLACE: 302 | place = childOfChild.get_value() 303 | 304 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_SOURCE: 305 | sources.append(childOfChild.get_value()) 306 | 307 | census.append((date, place, sources)) 308 | 309 | return census 310 | 311 | def get_last_change_date(self): 312 | """Returns the date of when the person data was last changed formatted as a string 313 | :rtype: str 314 | """ 315 | date = "" 316 | 317 | for child in self.get_child_elements(): 318 | if child.get_tag() == gedcom.tags.GEDCOM_TAG_CHANGE: 319 | for childOfChild in child.get_child_elements(): 320 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_DATE: 321 | date = childOfChild.get_value() 322 | 323 | return date 324 | 325 | def get_occupation(self): 326 | """Returns the occupation of a person 327 | :rtype: str 328 | """ 329 | occupation = "" 330 | 331 | for child in self.get_child_elements(): 332 | if child.get_tag() == gedcom.tags.GEDCOM_TAG_OCCUPATION: 333 | occupation = child.get_value() 334 | 335 | return occupation 336 | 337 | def birth_year_match(self, year): 338 | """Returns `True` if the given year matches the birth year of this person 339 | :type year: int 340 | :rtype: bool 341 | """ 342 | return self.get_birth_year() == year 343 | 344 | def birth_range_match(self, from_year, to_year): 345 | """Checks if the birth year of a person lies within the given range 346 | :type from_year: int 347 | :type to_year: int 348 | :rtype: bool 349 | """ 350 | birth_year = self.get_birth_year() 351 | 352 | if from_year <= birth_year <= to_year: 353 | return True 354 | 355 | return False 356 | 357 | def death_year_match(self, year): 358 | """Returns `True` if the given year matches the death year of this person 359 | :type year: int 360 | :rtype: bool 361 | """ 362 | return self.get_death_year() == year 363 | 364 | def death_range_match(self, from_year, to_year): 365 | """Checks if the death year of a person lies within the given range 366 | :type from_year: int 367 | :type to_year: int 368 | :rtype: bool 369 | """ 370 | death_year = self.get_death_year() 371 | 372 | if from_year <= death_year <= to_year: 373 | return True 374 | 375 | return False 376 | 377 | def criteria_match(self, criteria): 378 | """Checks if this individual matches all of the given criteria 379 | 380 | `criteria` is a colon-separated list, where each item in the 381 | list has the form [name]=[value]. The following criteria are supported: 382 | 383 | surname=[name] 384 | Match a person with [name] in any part of the `surname`. 385 | given_name=[given_name] 386 | Match a person with [given_name] in any part of the given `given_name`. 387 | birth=[year] 388 | Match a person whose birth year is a four-digit [year]. 389 | birth_range=[from_year-to_year] 390 | Match a person whose birth year is in the range of years from 391 | [from_year] to [to_year], including both [from_year] and [to_year]. 392 | 393 | :type criteria: str 394 | :rtype: bool 395 | """ 396 | 397 | # Check if criteria is a valid criteria and can be split by `:` and `=` characters 398 | try: 399 | for criterion in criteria.split(':'): 400 | criterion.split('=') 401 | except ValueError: 402 | return False 403 | 404 | match = True 405 | 406 | for criterion in criteria.split(':'): 407 | key, value = criterion.split('=') 408 | 409 | if key == "surname" and not self.surname_match(value): 410 | match = False 411 | elif key == "name" and not self.given_name_match(value): 412 | match = False 413 | elif key == "birth": 414 | 415 | try: 416 | year = int(value) 417 | if not self.birth_year_match(year): 418 | match = False 419 | except ValueError: 420 | match = False 421 | 422 | elif key == "birth_range": 423 | 424 | try: 425 | from_year, to_year = value.split('-') 426 | from_year = int(from_year) 427 | to_year = int(to_year) 428 | if not self.birth_range_match(from_year, to_year): 429 | match = False 430 | except ValueError: 431 | match = False 432 | 433 | elif key == "death": 434 | 435 | try: 436 | year = int(value) 437 | if not self.death_year_match(year): 438 | match = False 439 | except ValueError: 440 | match = False 441 | 442 | elif key == "death_range": 443 | 444 | try: 445 | from_year, to_year = value.split('-') 446 | from_year = int(from_year) 447 | to_year = int(to_year) 448 | if not self.death_range_match(from_year, to_year): 449 | match = False 450 | except ValueError: 451 | match = False 452 | 453 | return match 454 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /gedcom/parser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Python GEDCOM Parser 4 | # 5 | # Copyright (C) 2018 Damon Brodie (damon.brodie at gmail.com) 6 | # Copyright (C) 2018-2019 Nicklas Reincke (contact at reynke.com) 7 | # Copyright (C) 2016 Andreas Oberritter 8 | # Copyright (C) 2012 Madeleine Price Ball 9 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu) 10 | # Copyright (C) 2005 Brigham Young University 11 | # 12 | # This program is free software; you can redistribute it and/or modify 13 | # it under the terms of the GNU General Public License as published by 14 | # the Free Software Foundation; either version 2 of the License, or 15 | # (at your option) any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU General Public License along 23 | # with this program; if not, write to the Free Software Foundation, Inc., 24 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 25 | # 26 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html 27 | 28 | """ 29 | Module containing the actual `gedcom.parser.Parser` used to generate elements - out of each line - 30 | which can in return be manipulated. 31 | """ 32 | 33 | import re as regex 34 | from sys import version_info 35 | from gedcom.element.element import Element 36 | from gedcom.element.family import FamilyElement, NotAnActualFamilyError 37 | from gedcom.element.file import FileElement 38 | from gedcom.element.individual import IndividualElement, NotAnActualIndividualError 39 | from gedcom.element.object import ObjectElement 40 | from gedcom.element.root import RootElement 41 | import gedcom.tags 42 | 43 | FAMILY_MEMBERS_TYPE_ALL = "ALL" 44 | FAMILY_MEMBERS_TYPE_CHILDREN = gedcom.tags.GEDCOM_TAG_CHILD 45 | FAMILY_MEMBERS_TYPE_HUSBAND = gedcom.tags.GEDCOM_TAG_HUSBAND 46 | FAMILY_MEMBERS_TYPE_PARENTS = "PARENTS" 47 | FAMILY_MEMBERS_TYPE_WIFE = gedcom.tags.GEDCOM_TAG_WIFE 48 | 49 | 50 | class GedcomFormatViolationError(Exception): 51 | pass 52 | 53 | 54 | class Parser(object): 55 | """Parses and manipulates GEDCOM 5.5 format data 56 | 57 | For documentation of the GEDCOM 5.5 format, see: http://homepages.rootsweb.ancestry.com/~pmcbride/gedcom/55gctoc.htm 58 | 59 | This parser reads and parses a GEDCOM file. 60 | 61 | Elements may be accessed via: 62 | 63 | * a `list` through `gedcom.parser.Parser.get_element_list()` 64 | * a `dict` through `gedcom.parser.Parser.get_element_dictionary()` 65 | """ 66 | 67 | def __init__(self): 68 | self.__element_list = [] 69 | self.__element_dictionary = {} 70 | self.__root_element = RootElement() 71 | 72 | def invalidate_cache(self): 73 | """Empties the element list and dictionary to cause `gedcom.parser.Parser.get_element_list()` 74 | and `gedcom.parser.Parser.get_element_dictionary()` to return updated data. 75 | 76 | The update gets deferred until each of the methods actually gets called. 77 | """ 78 | self.__element_list = [] 79 | self.__element_dictionary = {} 80 | 81 | def get_element_list(self): 82 | """Returns a list containing all elements from within the GEDCOM file 83 | 84 | By default elements are in the same order as they appeared in the file. 85 | 86 | This list gets generated on-the-fly, but gets cached. If the database 87 | was modified, you should call `gedcom.parser.Parser.invalidate_cache()` once to let this 88 | method return updated data. 89 | 90 | Consider using `gedcom.parser.Parser.get_root_element()` or `gedcom.parser.Parser.get_root_child_elements()` to access 91 | the hierarchical GEDCOM tree, unless you rarely modify the database. 92 | 93 | :rtype: list of Element 94 | """ 95 | if not self.__element_list: 96 | for element in self.get_root_child_elements(): 97 | self.__build_list(element, self.__element_list) 98 | return self.__element_list 99 | 100 | def get_element_dictionary(self): 101 | """Returns a dictionary containing all elements, identified by a pointer, from within the GEDCOM file 102 | 103 | Only elements identified by a pointer are listed in the dictionary. 104 | The keys for the dictionary are the pointers. 105 | 106 | This dictionary gets generated on-the-fly, but gets cached. If the 107 | database was modified, you should call `invalidate_cache()` once to let 108 | this method return updated data. 109 | 110 | :rtype: dict of Element 111 | """ 112 | if not self.__element_dictionary: 113 | self.__element_dictionary = { 114 | element.get_pointer(): element for element in self.get_root_child_elements() if element.get_pointer() 115 | } 116 | 117 | return self.__element_dictionary 118 | 119 | def get_root_element(self): 120 | """Returns a virtual root element containing all logical records as children 121 | 122 | When printed, this element converts to an empty string. 123 | 124 | :rtype: RootElement 125 | """ 126 | return self.__root_element 127 | 128 | def get_root_child_elements(self): 129 | """Returns a list of logical records in the GEDCOM file 130 | 131 | By default, elements are in the same order as they appeared in the file. 132 | 133 | :rtype: list of Element 134 | """ 135 | return self.get_root_element().get_child_elements() 136 | 137 | def parse_file(self, file_path, strict=True): 138 | """Opens and parses a file, from the given file path, as GEDCOM 5.5 formatted data 139 | :type file_path: str 140 | :type strict: bool 141 | """ 142 | with open(file_path, 'rb') as gedcom_stream: 143 | self.parse(gedcom_stream, strict) 144 | 145 | def parse(self, gedcom_stream, strict=True): 146 | """Parses a stream, or an array of lines, as GEDCOM 5.5 formatted data 147 | :type gedcom_stream: a file stream, or str array of lines with new line at the end 148 | :type strict: bool 149 | """ 150 | self.invalidate_cache() 151 | self.__root_element = RootElement() 152 | 153 | line_number = 1 154 | last_element = self.get_root_element() 155 | 156 | for line in gedcom_stream: 157 | last_element = self.__parse_line(line_number, line.decode('utf-8-sig'), last_element, strict) 158 | line_number += 1 159 | 160 | # Private methods 161 | 162 | @staticmethod 163 | def __parse_line(line_number, line, last_element, strict=True): 164 | """Parse a line from a GEDCOM 5.5 formatted document 165 | 166 | Each line should have the following (bracketed items optional): 167 | level + ' ' + [pointer + ' ' +] tag + [' ' + line_value] 168 | 169 | :type line_number: int 170 | :type line: str 171 | :type last_element: Element 172 | :type strict: bool 173 | 174 | :rtype: Element 175 | """ 176 | 177 | # Level must start with non-negative int, no leading zeros. 178 | level_regex = '^(0|[1-9]+[0-9]*) ' 179 | 180 | # Pointer optional, if it exists it must be flanked by `@` 181 | pointer_regex = '(@[^@]+@ |)' 182 | 183 | # Tag must be an alphanumeric string 184 | tag_regex = '([A-Za-z0-9_]+)' 185 | 186 | # Value optional, consists of anything after a space to end of line 187 | value_regex = '( [^\n\r]*|)' 188 | 189 | # End of line defined by `\n` or `\r` 190 | end_of_line_regex = '([\r\n]{1,2})' 191 | 192 | # Complete regex 193 | gedcom_line_regex = level_regex + pointer_regex + tag_regex + value_regex + end_of_line_regex 194 | regex_match = regex.match(gedcom_line_regex, line) 195 | 196 | if regex_match is None: 197 | if strict: 198 | error_message = ("Line <%d:%s> of document violates GEDCOM format 5.5" % (line_number, line) 199 | + "\nSee: https://chronoplexsoftware.com/gedcomvalidator/gedcom/gedcom-5.5.pdf") 200 | raise GedcomFormatViolationError(error_message) 201 | else: 202 | # Quirk check - see if this is a line without a CRLF (which could be the last line) 203 | last_line_regex = level_regex + pointer_regex + tag_regex + value_regex 204 | regex_match = regex.match(last_line_regex, line) 205 | if regex_match is not None: 206 | line_parts = regex_match.groups() 207 | 208 | level = int(line_parts[0]) 209 | pointer = line_parts[1].rstrip(' ') 210 | tag = line_parts[2] 211 | value = line_parts[3][1:] 212 | crlf = '\n' 213 | else: 214 | # Quirk check - Sometimes a gedcom has a text field with a CR. 215 | # This creates a line without the standard level and pointer. 216 | # If this is detected then turn it into a CONC or CONT. 217 | line_regex = '([^\n\r]*|)' 218 | cont_line_regex = line_regex + end_of_line_regex 219 | regex_match = regex.match(cont_line_regex, line) 220 | line_parts = regex_match.groups() 221 | level = last_element.get_level() 222 | tag = last_element.get_tag() 223 | pointer = None 224 | value = line_parts[0][1:] 225 | crlf = line_parts[1] 226 | if tag != gedcom.tags.GEDCOM_TAG_CONTINUED and tag != gedcom.tags.GEDCOM_TAG_CONCATENATION: 227 | # Increment level and change this line to a CONC 228 | level += 1 229 | tag = gedcom.tags.GEDCOM_TAG_CONCATENATION 230 | else: 231 | line_parts = regex_match.groups() 232 | 233 | level = int(line_parts[0]) 234 | pointer = line_parts[1].rstrip(' ') 235 | tag = line_parts[2] 236 | value = line_parts[3][1:] 237 | crlf = line_parts[4] 238 | 239 | # Check level: should never be more than one higher than previous line. 240 | if level > last_element.get_level() + 1: 241 | error_message = ("Line %d of document violates GEDCOM format 5.5" % line_number 242 | + "\nLines must be no more than one level higher than previous line." 243 | + "\nSee: https://chronoplexsoftware.com/gedcomvalidator/gedcom/gedcom-5.5.pdf") 244 | raise GedcomFormatViolationError(error_message) 245 | 246 | # Create element. Store in list and dict, create children and parents. 247 | if tag == gedcom.tags.GEDCOM_TAG_INDIVIDUAL: 248 | element = IndividualElement(level, pointer, tag, value, crlf, multi_line=False) 249 | elif tag == gedcom.tags.GEDCOM_TAG_FAMILY: 250 | element = FamilyElement(level, pointer, tag, value, crlf, multi_line=False) 251 | elif tag == gedcom.tags.GEDCOM_TAG_FILE: 252 | element = FileElement(level, pointer, tag, value, crlf, multi_line=False) 253 | elif tag == gedcom.tags.GEDCOM_TAG_OBJECT: 254 | element = ObjectElement(level, pointer, tag, value, crlf, multi_line=False) 255 | else: 256 | element = Element(level, pointer, tag, value, crlf, multi_line=False) 257 | 258 | # Start with last element as parent, back up if necessary. 259 | parent_element = last_element 260 | 261 | while parent_element.get_level() > level - 1: 262 | parent_element = parent_element.get_parent_element() 263 | 264 | # Add child to parent & parent to child. 265 | parent_element.add_child_element(element) 266 | 267 | return element 268 | 269 | def __build_list(self, element, element_list): 270 | """Recursively add elements to a list containing elements 271 | :type element: Element 272 | :type element_list: list of Element 273 | """ 274 | element_list.append(element) 275 | for child in element.get_child_elements(): 276 | self.__build_list(child, element_list) 277 | 278 | # Methods for analyzing individuals and relationships between individuals 279 | 280 | def get_marriages(self, individual): 281 | """Returns a list of marriages of an individual formatted as a tuple (`str` date, `str` place) 282 | :type individual: IndividualElement 283 | :rtype: tuple 284 | """ 285 | marriages = [] 286 | if not isinstance(individual, IndividualElement): 287 | raise NotAnActualIndividualError( 288 | "Operation only valid for elements with %s tag" % gedcom.tags.GEDCOM_TAG_INDIVIDUAL 289 | ) 290 | # Get and analyze families where individual is spouse. 291 | families = self.get_families(individual, gedcom.tags.GEDCOM_TAG_FAMILY_SPOUSE) 292 | for family in families: 293 | for family_data in family.get_child_elements(): 294 | if family_data.get_tag() == gedcom.tags.GEDCOM_TAG_MARRIAGE: 295 | date = '' 296 | place = '' 297 | for marriage_data in family_data.get_child_elements(): 298 | if marriage_data.get_tag() == gedcom.tags.GEDCOM_TAG_DATE: 299 | date = marriage_data.get_value() 300 | if marriage_data.get_tag() == gedcom.tags.GEDCOM_TAG_PLACE: 301 | place = marriage_data.get_value() 302 | marriages.append((date, place)) 303 | return marriages 304 | 305 | def get_marriage_years(self, individual): 306 | """Returns a list of marriage years (as integers) for an individual 307 | :type individual: IndividualElement 308 | :rtype: list of int 309 | """ 310 | dates = [] 311 | 312 | if not isinstance(individual, IndividualElement): 313 | raise NotAnActualIndividualError( 314 | "Operation only valid for elements with %s tag" % gedcom.tags.GEDCOM_TAG_INDIVIDUAL 315 | ) 316 | 317 | # Get and analyze families where individual is spouse. 318 | families = self.get_families(individual, gedcom.tags.GEDCOM_TAG_FAMILY_SPOUSE) 319 | for family in families: 320 | for child in family.get_child_elements(): 321 | if child.get_tag() == gedcom.tags.GEDCOM_TAG_MARRIAGE: 322 | for childOfChild in child.get_child_elements(): 323 | if childOfChild.get_tag() == gedcom.tags.GEDCOM_TAG_DATE: 324 | date = childOfChild.get_value().split()[-1] 325 | try: 326 | dates.append(int(date)) 327 | except ValueError: 328 | pass 329 | return dates 330 | 331 | def marriage_year_match(self, individual, year): 332 | """Checks if one of the marriage years of an individual matches the supplied year. Year is an integer. 333 | :type individual: IndividualElement 334 | :type year: int 335 | :rtype: bool 336 | """ 337 | if not isinstance(individual, IndividualElement): 338 | raise NotAnActualIndividualError( 339 | "Operation only valid for elements with %s tag" % gedcom.tags.GEDCOM_TAG_INDIVIDUAL 340 | ) 341 | 342 | years = self.get_marriage_years(individual) 343 | return year in years 344 | 345 | def marriage_range_match(self, individual, from_year, to_year): 346 | """Check if one of the marriage years of an individual is in a given range. Years are integers. 347 | :type individual: IndividualElement 348 | :type from_year: int 349 | :type to_year: int 350 | :rtype: bool 351 | """ 352 | if not isinstance(individual, IndividualElement): 353 | raise NotAnActualIndividualError( 354 | "Operation only valid for elements with %s tag" % gedcom.tags.GEDCOM_TAG_INDIVIDUAL 355 | ) 356 | 357 | years = self.get_marriage_years(individual) 358 | for year in years: 359 | if from_year <= year <= to_year: 360 | return True 361 | return False 362 | 363 | def get_families(self, individual, family_type=gedcom.tags.GEDCOM_TAG_FAMILY_SPOUSE): 364 | """Return family elements listed for an individual 365 | 366 | family_type can be `gedcom.tags.GEDCOM_TAG_FAMILY_SPOUSE` (families where the individual is a spouse) or 367 | `gedcom.tags.GEDCOM_TAG_FAMILY_CHILD` (families where the individual is a child). If a value is not 368 | provided, `gedcom.tags.GEDCOM_TAG_FAMILY_SPOUSE` is default value. 369 | 370 | :type individual: IndividualElement 371 | :type family_type: str 372 | :rtype: list of FamilyElement 373 | """ 374 | if not isinstance(individual, IndividualElement): 375 | raise NotAnActualIndividualError( 376 | "Operation only valid for elements with %s tag" % gedcom.tags.GEDCOM_TAG_INDIVIDUAL 377 | ) 378 | 379 | families = [] 380 | element_dictionary = self.get_element_dictionary() 381 | 382 | for child_element in individual.get_child_elements(): 383 | is_family = (child_element.get_tag() == family_type 384 | and child_element.get_value() in element_dictionary) 385 | if is_family: 386 | families.append(element_dictionary[child_element.get_value()]) 387 | 388 | return families 389 | 390 | def get_ancestors(self, individual, ancestor_type="ALL"): 391 | """Return elements corresponding to ancestors of an individual 392 | 393 | Optional `ancestor_type`. Default "ALL" returns all ancestors, "NAT" can be 394 | used to specify only natural (genetic) ancestors. 395 | 396 | :type individual: IndividualElement 397 | :type ancestor_type: str 398 | :rtype: list of Element 399 | """ 400 | if not isinstance(individual, IndividualElement): 401 | raise NotAnActualIndividualError( 402 | "Operation only valid for elements with %s tag" % gedcom.tags.GEDCOM_TAG_INDIVIDUAL 403 | ) 404 | 405 | parents = self.get_parents(individual, ancestor_type) 406 | ancestors = [] 407 | ancestors.extend(parents) 408 | 409 | for parent in parents: 410 | ancestors.extend(self.get_ancestors(parent)) 411 | 412 | return ancestors 413 | 414 | def get_parents(self, individual, parent_type="ALL"): 415 | """Return elements corresponding to parents of an individual 416 | 417 | Optional parent_type. Default "ALL" returns all parents. "NAT" can be 418 | used to specify only natural (genetic) parents. 419 | 420 | :type individual: IndividualElement 421 | :type parent_type: str 422 | :rtype: list of IndividualElement 423 | """ 424 | if not isinstance(individual, IndividualElement): 425 | raise NotAnActualIndividualError( 426 | "Operation only valid for elements with %s tag" % gedcom.tags.GEDCOM_TAG_INDIVIDUAL 427 | ) 428 | 429 | parents = [] 430 | families = self.get_families(individual, gedcom.tags.GEDCOM_TAG_FAMILY_CHILD) 431 | 432 | for family in families: 433 | if parent_type == "NAT": 434 | for family_member in family.get_child_elements(): 435 | 436 | if family_member.get_tag() == gedcom.tags.GEDCOM_TAG_CHILD \ 437 | and family_member.get_value() == individual.get_pointer(): 438 | 439 | for child in family_member.get_child_elements(): 440 | if child.get_value() == "Natural": 441 | if child.get_tag() == gedcom.tags.GEDCOM_PROGRAM_DEFINED_TAG_MREL: 442 | parents += self.get_family_members(family, gedcom.tags.GEDCOM_TAG_WIFE) 443 | elif child.get_tag() == gedcom.tags.GEDCOM_PROGRAM_DEFINED_TAG_FREL: 444 | parents += self.get_family_members(family, gedcom.tags.GEDCOM_TAG_HUSBAND) 445 | else: 446 | parents += self.get_family_members(family, "PARENTS") 447 | 448 | return parents 449 | 450 | def find_path_to_ancestor(self, descendant, ancestor, path=None): 451 | """Return path from descendant to ancestor 452 | :rtype: object 453 | """ 454 | if not isinstance(descendant, IndividualElement) and isinstance(ancestor, IndividualElement): 455 | raise NotAnActualIndividualError( 456 | "Operation only valid for elements with %s tag." % gedcom.tags.GEDCOM_TAG_INDIVIDUAL 457 | ) 458 | 459 | if not path: 460 | path = [descendant] 461 | 462 | if path[-1].get_pointer() == ancestor.get_pointer(): 463 | return path 464 | else: 465 | parents = self.get_parents(descendant, "NAT") 466 | for parent in parents: 467 | potential_path = self.find_path_to_ancestor(parent, ancestor, path + [parent]) 468 | if potential_path is not None: 469 | return potential_path 470 | 471 | return None 472 | 473 | def get_family_members(self, family, members_type=FAMILY_MEMBERS_TYPE_ALL): 474 | """Return array of family members: individual, spouse, and children 475 | 476 | Optional argument `members_type` can be used to return specific subsets: 477 | 478 | "FAMILY_MEMBERS_TYPE_ALL": Default, return all members of the family 479 | "FAMILY_MEMBERS_TYPE_PARENTS": Return individuals with "HUSB" and "WIFE" tags (parents) 480 | "FAMILY_MEMBERS_TYPE_HUSBAND": Return individuals with "HUSB" tags (father) 481 | "FAMILY_MEMBERS_TYPE_WIFE": Return individuals with "WIFE" tags (mother) 482 | "FAMILY_MEMBERS_TYPE_CHILDREN": Return individuals with "CHIL" tags (children) 483 | 484 | :type family: FamilyElement 485 | :type members_type: str 486 | :rtype: list of IndividualElement 487 | """ 488 | if not isinstance(family, FamilyElement): 489 | raise NotAnActualFamilyError( 490 | "Operation only valid for element with %s tag." % gedcom.tags.GEDCOM_TAG_FAMILY 491 | ) 492 | 493 | family_members = [] 494 | element_dictionary = self.get_element_dictionary() 495 | 496 | for child_element in family.get_child_elements(): 497 | # Default is ALL 498 | is_family = (child_element.get_tag() == gedcom.tags.GEDCOM_TAG_HUSBAND 499 | or child_element.get_tag() == gedcom.tags.GEDCOM_TAG_WIFE 500 | or child_element.get_tag() == gedcom.tags.GEDCOM_TAG_CHILD) 501 | 502 | if members_type == FAMILY_MEMBERS_TYPE_PARENTS: 503 | is_family = (child_element.get_tag() == gedcom.tags.GEDCOM_TAG_HUSBAND 504 | or child_element.get_tag() == gedcom.tags.GEDCOM_TAG_WIFE) 505 | elif members_type == FAMILY_MEMBERS_TYPE_HUSBAND: 506 | is_family = child_element.get_tag() == gedcom.tags.GEDCOM_TAG_HUSBAND 507 | elif members_type == FAMILY_MEMBERS_TYPE_WIFE: 508 | is_family = child_element.get_tag() == gedcom.tags.GEDCOM_TAG_WIFE 509 | elif members_type == FAMILY_MEMBERS_TYPE_CHILDREN: 510 | is_family = child_element.get_tag() == gedcom.tags.GEDCOM_TAG_CHILD 511 | 512 | if is_family and child_element.get_value() in element_dictionary: 513 | family_members.append(element_dictionary[child_element.get_value()]) 514 | 515 | return family_members 516 | 517 | # Other methods 518 | 519 | def print_gedcom(self): 520 | """Write GEDCOM data to stdout""" 521 | from sys import stdout 522 | self.save_gedcom(stdout) 523 | 524 | def save_gedcom(self, open_file): 525 | """Save GEDCOM data to a file 526 | :type open_file: file 527 | """ 528 | if version_info[0] >= 3: 529 | open_file.write(self.get_root_element().to_gedcom_string(True)) 530 | else: 531 | open_file.write(self.get_root_element().to_gedcom_string(True).encode('utf-8-sig')) 532 | --------------------------------------------------------------------------------