├── 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 |
7 |
8 |
9 |
10 |
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
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 | ]
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)
# -*- 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
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
# -*- 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
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
# -*- 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
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
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
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
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 |
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:
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 | ]
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 |
--------------------------------------------------------------------------------