├── .gitignore ├── LICENSE ├── README.md ├── gedcom └── __init__.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore .ged files 2 | *.ged 3 | 4 | # Created by .ignore support plugin (hsz.mobi) 5 | ### JetBrains template 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff: 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/dictionaries 13 | 14 | # Sensitive or high-churn files: 15 | .idea/**/dataSources/ 16 | .idea/**/dataSources.ids 17 | .idea/**/dataSources.xml 18 | .idea/**/dataSources.local.xml 19 | .idea/**/sqlDataSources.xml 20 | .idea/**/dynamic.xml 21 | .idea/**/uiDesigner.xml 22 | 23 | # Gradle: 24 | .idea/**/gradle.xml 25 | .idea/**/libraries 26 | 27 | # CMake 28 | cmake-build-debug/ 29 | 30 | # Mongo Explorer plugin: 31 | .idea/**/mongoSettings.xml 32 | 33 | ## File-based project format: 34 | *.iws 35 | 36 | ## Plugin-specific files: 37 | 38 | # IntelliJ 39 | out/ 40 | 41 | # mpeltonen/sbt-idea plugin 42 | .idea_modules/ 43 | 44 | # JIRA plugin 45 | atlassian-ide-plugin.xml 46 | 47 | # Cursive Clojure plugin 48 | .idea/replstate.xml 49 | 50 | # Crashlytics plugin (for Android Studio and IntelliJ) 51 | com_crashlytics_export_strings.xml 52 | crashlytics.properties 53 | crashlytics-build.properties 54 | fabric.properties 55 | ### VisualStudio template 56 | ## Ignore Visual Studio temporary files, build results, and 57 | ## files generated by popular Visual Studio add-ons. 58 | ## 59 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 60 | 61 | # User-specific files 62 | *.suo 63 | *.user 64 | *.userosscache 65 | *.sln.docstates 66 | 67 | # User-specific files (MonoDevelop/Xamarin Studio) 68 | *.userprefs 69 | 70 | # Build results 71 | [Dd]ebug/ 72 | [Dd]ebugPublic/ 73 | [Rr]elease/ 74 | [Rr]eleases/ 75 | x64/ 76 | x86/ 77 | bld/ 78 | [Bb]in/ 79 | [Oo]bj/ 80 | [Ll]og/ 81 | 82 | # Visual Studio 2015 cache/options directory 83 | .vs/ 84 | # Uncomment if you have tasks that create the project's static files in wwwroot 85 | #wwwroot/ 86 | 87 | # MSTest test Results 88 | [Tt]est[Rr]esult*/ 89 | [Bb]uild[Ll]og.* 90 | 91 | # NUNIT 92 | *.VisualState.xml 93 | TestResult.xml 94 | 95 | # Build Results of an ATL Project 96 | [Dd]ebugPS/ 97 | [Rr]eleasePS/ 98 | dlldata.c 99 | 100 | # Benchmark Results 101 | BenchmarkDotNet.Artifacts/ 102 | 103 | # .NET Core 104 | project.lock.json 105 | project.fragment.lock.json 106 | artifacts/ 107 | **/Properties/launchSettings.json 108 | 109 | *_i.c 110 | *_p.c 111 | *_i.h 112 | *.ilk 113 | *.meta 114 | *.obj 115 | *.pch 116 | *.pdb 117 | *.pgc 118 | *.pgd 119 | *.rsp 120 | *.sbr 121 | *.tlb 122 | *.tli 123 | *.tlh 124 | *.tmp 125 | *.tmp_proj 126 | *.log 127 | *.vspscc 128 | *.vssscc 129 | .builds 130 | *.pidb 131 | *.svclog 132 | *.scc 133 | 134 | # Chutzpah Test files 135 | _Chutzpah* 136 | 137 | # Visual C++ cache files 138 | ipch/ 139 | *.aps 140 | *.ncb 141 | *.opendb 142 | *.opensdf 143 | *.sdf 144 | *.cachefile 145 | *.VC.db 146 | *.VC.VC.opendb 147 | 148 | # Visual Studio profiler 149 | *.psess 150 | *.vsp 151 | *.vspx 152 | *.sap 153 | 154 | # Visual Studio Trace Files 155 | *.e2e 156 | 157 | # TFS 2012 Local Workspace 158 | $tf/ 159 | 160 | # Guidance Automation Toolkit 161 | *.gpState 162 | 163 | # ReSharper is a .NET coding add-in 164 | _ReSharper*/ 165 | *.[Rr]e[Ss]harper 166 | *.DotSettings.user 167 | 168 | # JustCode is a .NET coding add-in 169 | .JustCode 170 | 171 | # TeamCity is a build add-in 172 | _TeamCity* 173 | 174 | # DotCover is a Code Coverage Tool 175 | *.dotCover 176 | 177 | # AxoCover is a Code Coverage Tool 178 | .axoCover/* 179 | !.axoCover/settings.json 180 | 181 | # Visual Studio code coverage results 182 | *.coverage 183 | *.coveragexml 184 | 185 | # NCrunch 186 | _NCrunch_* 187 | .*crunch*.local.xml 188 | nCrunchTemp_* 189 | 190 | # MightyMoose 191 | *.mm.* 192 | AutoTest.Net/ 193 | 194 | # Web workbench (sass) 195 | .sass-cache/ 196 | 197 | # Installshield output folder 198 | [Ee]xpress/ 199 | 200 | # DocProject is a documentation generator add-in 201 | DocProject/buildhelp/ 202 | DocProject/Help/*.HxT 203 | DocProject/Help/*.HxC 204 | DocProject/Help/*.hhc 205 | DocProject/Help/*.hhk 206 | DocProject/Help/*.hhp 207 | DocProject/Help/Html2 208 | DocProject/Help/html 209 | 210 | # Click-Once directory 211 | publish/ 212 | 213 | # Publish Web Output 214 | *.[Pp]ublish.xml 215 | *.azurePubxml 216 | # Note: Comment the next line if you want to checkin your web deploy settings, 217 | # but database connection strings (with potential passwords) will be unencrypted 218 | *.pubxml 219 | *.publishproj 220 | 221 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 222 | # checkin your Azure Web App publish settings, but sensitive information contained 223 | # in these scripts will be unencrypted 224 | PublishScripts/ 225 | 226 | # NuGet Packages 227 | *.nupkg 228 | # The packages folder can be ignored because of Package Restore 229 | **/[Pp]ackages/* 230 | # except build/, which is used as an MSBuild target. 231 | !**/[Pp]ackages/build/ 232 | # Uncomment if necessary however generally it will be regenerated when needed 233 | #!**/[Pp]ackages/repositories.config 234 | # NuGet v3's project.json files produces more ignorable files 235 | *.nuget.props 236 | *.nuget.targets 237 | 238 | # Microsoft Azure Build Output 239 | csx/ 240 | *.build.csdef 241 | 242 | # Microsoft Azure Emulator 243 | ecf/ 244 | rcf/ 245 | 246 | # Windows Store app package directories and files 247 | AppPackages/ 248 | BundleArtifacts/ 249 | Package.StoreAssociation.xml 250 | _pkginfo.txt 251 | *.appx 252 | 253 | # Visual Studio cache files 254 | # files ending in .cache can be ignored 255 | *.[Cc]ache 256 | # but keep track of directories ending in .cache 257 | !*.[Cc]ache/ 258 | 259 | # Others 260 | ClientBin/ 261 | ~$* 262 | *~ 263 | *.dbmdl 264 | *.dbproj.schemaview 265 | *.jfm 266 | *.pfx 267 | *.publishsettings 268 | orleans.codegen.cs 269 | 270 | # Since there are multiple workflows, uncomment next line to ignore bower_components 271 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 272 | #bower_components/ 273 | 274 | # RIA/Silverlight projects 275 | Generated_Code/ 276 | 277 | # Backup & report files from converting an old project file 278 | # to a newer Visual Studio version. Backup files are not needed, 279 | # because we have git ;-) 280 | _UpgradeReport_Files/ 281 | Backup*/ 282 | UpgradeLog*.XML 283 | UpgradeLog*.htm 284 | 285 | # SQL Server files 286 | *.mdf 287 | *.ldf 288 | *.ndf 289 | 290 | # Business Intelligence projects 291 | *.rdl.data 292 | *.bim.layout 293 | *.bim_*.settings 294 | 295 | # Microsoft Fakes 296 | FakesAssemblies/ 297 | 298 | # GhostDoc plugin setting file 299 | *.GhostDoc.xml 300 | 301 | # Node.js Tools for Visual Studio 302 | .ntvs_analysis.dat 303 | node_modules/ 304 | 305 | # Typescript v1 declaration files 306 | typings/ 307 | 308 | # Visual Studio 6 build log 309 | *.plg 310 | 311 | # Visual Studio 6 workspace options file 312 | *.opt 313 | 314 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 315 | *.vbw 316 | 317 | # Visual Studio LightSwitch build output 318 | **/*.HTMLClient/GeneratedArtifacts 319 | **/*.DesktopClient/GeneratedArtifacts 320 | **/*.DesktopClient/ModelManifest.xml 321 | **/*.Server/GeneratedArtifacts 322 | **/*.Server/ModelManifest.xml 323 | _Pvt_Extensions 324 | 325 | # Paket dependency manager 326 | .paket/paket.exe 327 | paket-files/ 328 | 329 | # FAKE - F# Make 330 | .fake/ 331 | 332 | # JetBrains Rider 333 | .idea/ 334 | *.sln.iml 335 | 336 | # CodeRush 337 | .cr/ 338 | 339 | # Python Tools for Visual Studio (PTVS) 340 | __pycache__/ 341 | *.pyc 342 | 343 | # Cake - Uncomment if you are using it 344 | # tools/** 345 | # !tools/packages.config 346 | 347 | # Tabs Studio 348 | *.tss 349 | 350 | # Telerik's JustMock configuration file 351 | *.jmconfig 352 | 353 | # BizTalk build output 354 | *.btp.cs 355 | *.btm.cs 356 | *.odx.cs 357 | *.xsd.cs 358 | 359 | # OpenCover UI analysis results 360 | OpenCover/ 361 | ### SublimeText template 362 | # Cache files for Sublime Text 363 | *.tmlanguage.cache 364 | *.tmPreferences.cache 365 | *.stTheme.cache 366 | 367 | # Workspace files are user-specific 368 | *.sublime-workspace 369 | 370 | # Project files should be checked into the repository, unless a significant 371 | # proportion of contributors will probably not be using Sublime Text 372 | # *.sublime-project 373 | 374 | # SFTP configuration file 375 | sftp-config.json 376 | 377 | # Package control specific files 378 | Package Control.last-run 379 | Package Control.ca-list 380 | Package Control.ca-bundle 381 | Package Control.system-ca-bundle 382 | Package Control.cache/ 383 | Package Control.ca-certs/ 384 | Package Control.merged-ca-bundle 385 | Package Control.user-ca-bundle 386 | oscrypto-ca-bundle.crt 387 | bh_unicode_properties.cache 388 | 389 | # Sublime-github package stores a github token in this file 390 | # https://packagecontrol.io/packages/sublime-github 391 | GitHub.sublime-settings 392 | ### VisualStudioCode template 393 | .vscode/* 394 | !.vscode/settings.json 395 | !.vscode/tasks.json 396 | !.vscode/launch.json 397 | !.vscode/extensions.json 398 | ### Python template 399 | # Byte-compiled / optimized / DLL files 400 | *.py[cod] 401 | *$py.class 402 | 403 | # C extensions 404 | *.so 405 | 406 | # Distribution / packaging 407 | .Python 408 | build/ 409 | develop-eggs/ 410 | dist/ 411 | downloads/ 412 | eggs/ 413 | .eggs/ 414 | lib/ 415 | lib64/ 416 | parts/ 417 | sdist/ 418 | var/ 419 | wheels/ 420 | *.egg-info/ 421 | .installed.cfg 422 | *.egg 423 | MANIFEST 424 | 425 | # PyInstaller 426 | # Usually these files are written by a python script from a template 427 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 428 | *.manifest 429 | *.spec 430 | 431 | # Installer logs 432 | pip-log.txt 433 | pip-delete-this-directory.txt 434 | 435 | # Unit test / coverage reports 436 | htmlcov/ 437 | .tox/ 438 | .coverage 439 | .coverage.* 440 | .cache 441 | nosetests.xml 442 | coverage.xml 443 | *.cover 444 | .hypothesis/ 445 | 446 | # Translations 447 | *.mo 448 | *.pot 449 | 450 | # Django stuff: 451 | .static_storage/ 452 | .media/ 453 | local_settings.py 454 | 455 | # Flask stuff: 456 | instance/ 457 | .webassets-cache 458 | 459 | # Scrapy stuff: 460 | .scrapy 461 | 462 | # Sphinx documentation 463 | docs/_build/ 464 | 465 | # PyBuilder 466 | target/ 467 | 468 | # Jupyter Notebook 469 | .ipynb_checkpoints 470 | 471 | # pyenv 472 | .python-version 473 | 474 | # celery beat schedule file 475 | celerybeat-schedule 476 | 477 | # SageMath parsed files 478 | *.sage.py 479 | 480 | # Environments 481 | .env 482 | .venv 483 | env/ 484 | venv/ 485 | ENV/ 486 | env.bak/ 487 | venv.bak/ 488 | 489 | # Spyder project settings 490 | .spyderproject 491 | .spyproject 492 | 493 | # Rope project settings 494 | .ropeproject 495 | 496 | # mkdocs documentation 497 | /site 498 | 499 | # mypy 500 | .mypy_cache/ 501 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python GEDCOM 2 | 3 | > **DEPRECATED**: This project is no longer maintained by madprime 4 | > and moved to https://github.com/joeyaurel/python-gedcom. 5 | 6 | A python module for parsing, analyzing, and manipulating GEDCOM files. 7 | 8 | GEDCOM files contain ancestry data, the GEDCOM 5.5 format is detailed here: 9 | http://homepages.rootsweb.ancestry.com/~pmcbride/gedcom/55gctoc.htm 10 | 11 | This module was originally based on a GEDCOM parser written by 12 | Daniel Zappala at Brigham Young University (copyright (C) 2005) and 13 | was licensed under GPL v2. 14 | -------------------------------------------------------------------------------- /gedcom/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # GEDCOM 5.5 Parser 3 | # 4 | # Copyright (C) 2018 Nicklas Reincke (contact [ at ] reynke.com) 5 | # Copyright (C) 2016 Andreas Oberritter 6 | # Copyright (C) 2012 Madeleine Price Ball 7 | # Copyright (C) 2005 Daniel Zappala (zappala [ at ] cs.byu.edu) 8 | # Copyright (C) 2005 Brigham Young University 9 | # 10 | # This program is free software; you can redistribute it and/or 11 | # modify it under the terms of the GNU General Public License 12 | # as published by the Free Software Foundation; either version 2 13 | # of the License, or (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program; if not, write to the Free Software 22 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 23 | # 24 | # Please see the GPL license at http://www.gnu.org/licenses/gpl.txt 25 | # 26 | # This code based on work from Zappala, 2005. 27 | # To contact the Zappala, see http://faculty.cs.byu.edu/~zappala 28 | 29 | __all__ = ["Gedcom", "Element", "GedcomParseError"] 30 | 31 | # Global imports 32 | import re as regex 33 | from sys import version_info 34 | 35 | 36 | class Gedcom: 37 | """Parses and manipulates GEDCOM 5.5 format data 38 | 39 | For documentation of the GEDCOM 5.5 format, see: 40 | http://homepages.rootsweb.ancestry.com/~pmcbride/gedcom/55gctoc.htm 41 | 42 | This parser reads and parses a GEDCOM file. 43 | Elements may be accessed via: 44 | - a list (all elements, default order is same as in file) 45 | - a dict (only elements with pointers, which are the keys) 46 | """ 47 | 48 | def __init__(self, file_path): 49 | """Initialize a GEDCOM data object. You must supply a GEDCOM file""" 50 | self.__element_list = [] 51 | self.__element_dict = {} 52 | self.invalidate_cache() 53 | self.__element_top = Element(-1, "", "TOP", "") 54 | self.__parse(file_path) 55 | 56 | def invalidate_cache(self): 57 | """Cause element_list() and element_dict() to return updated data 58 | 59 | The update gets deferred until each of the methods actually gets called. 60 | """ 61 | self.__element_list = [] 62 | self.__element_dict = {} 63 | 64 | def element_list(self): 65 | """Return a list of all the elements in the GEDCOM file 66 | 67 | By default elements are in the same order as they appeared in the file. 68 | 69 | This list gets generated on-the-fly, but gets cached. If the database 70 | was modified, you should call invalidate_cache() once to let this 71 | method return updated data. 72 | 73 | Consider using root() or records() to access the hierarchical GEDCOM 74 | tree, unless you rarely modify the database. 75 | """ 76 | if not self.__element_list: 77 | for element in self.records(): 78 | self.__build_list(element, self.__element_list) 79 | return self.__element_list 80 | 81 | def element_dict(self): 82 | """Return a dictionary of elements from the GEDCOM file 83 | 84 | Only elements identified by a pointer are listed in the dictionary. 85 | The keys for the dictionary are the pointers. 86 | 87 | This dictionary gets generated on-the-fly, but gets cached. If the 88 | database was modified, you should call invalidate_cache() once to let 89 | this method return updated data. 90 | """ 91 | if not self.__element_dict: 92 | self.__element_dict = {element.pointer(): element for element in self.records() if element.pointer()} 93 | return self.__element_dict 94 | 95 | def root(self): 96 | """Returns a virtual root element containing all logical records as children 97 | 98 | When printed, this element converts to an empty string. 99 | """ 100 | return self.__element_top 101 | 102 | def records(self): 103 | """Return a list of logical records in the GEDCOM file 104 | 105 | By default, elements are in the same order as they appeared in the file. 106 | """ 107 | return self.root().children() 108 | 109 | # Private methods 110 | 111 | def __parse(self, file_path): 112 | """Open and parse file path as GEDCOM 5.5 formatted data""" 113 | gedcom_file = open(file_path, 'rb') 114 | line_number = 1 115 | last_element = self.__element_top 116 | for line in gedcom_file: 117 | last_element = self.__parse_line(line_number, line.decode('utf-8'), last_element) 118 | line_number += 1 119 | 120 | def __parse_line(self, line_num, line, last_elem): 121 | """Parse a line from a GEDCOM 5.5 formatted document 122 | 123 | Each line should have the following (bracketed items optional): 124 | level + ' ' + [pointer + ' ' +] tag + [' ' + line_value] 125 | """ 126 | ged_line_regex = ( 127 | # Level must start with nonnegative int, no leading zeros. 128 | '^(0|[1-9]+[0-9]*) ' + 129 | # Pointer optional, if it exists it must be flanked by '@' 130 | '(@[^@]+@ |)' + 131 | # Tag must be alphanumeric string 132 | '([A-Za-z0-9_]+)' + 133 | # Value optional, consists of anything after a space to end of line 134 | '( [^\n\r]*|)' + 135 | # End of line defined by \n or \r 136 | '([\r\n]{1,2})' 137 | ) 138 | if regex.match(ged_line_regex, line): 139 | line_parts = regex.match(ged_line_regex, line).groups() 140 | else: 141 | error_message = ("Line %d of document violates GEDCOM format" % line_num + 142 | "\nSee: http://homepages.rootsweb.ancestry.com/" + 143 | "~pmcbride/gedcom/55gctoc.htm") 144 | raise SyntaxError(error_message) 145 | 146 | level = int(line_parts[0]) 147 | pointer = line_parts[1].rstrip(' ') 148 | tag = line_parts[2] 149 | value = line_parts[3][1:] 150 | crlf = line_parts[4] 151 | 152 | # Check level: should never be more than one higher than previous line. 153 | if level > last_elem.level() + 1: 154 | error_message = ("Line %d of document violates GEDCOM format" % line_num + 155 | "\nLines must be no more than one level higher than " + 156 | "previous line.\nSee: http://homepages.rootsweb." + 157 | "ancestry.com/~pmcbride/gedcom/55gctoc.htm") 158 | raise SyntaxError(error_message) 159 | 160 | # Create element. Store in list and dict, create children and parents. 161 | element = Element(level, pointer, tag, value, crlf, multi_line=False) 162 | 163 | # Start with last element as parent, back up if necessary. 164 | parent_elem = last_elem 165 | while parent_elem.level() > level - 1: 166 | parent_elem = parent_elem.parent() 167 | # Add child to parent & parent to child. 168 | parent_elem.add_child(element) 169 | return element 170 | 171 | def __build_list(self, element, element_list): 172 | """Recursively add Elements to a list containing elements""" 173 | element_list.append(element) 174 | for child in element.children(): 175 | self.__build_list(child, element_list) 176 | 177 | # Methods for analyzing individuals and relationships between individuals 178 | 179 | def marriages(self, individual): 180 | """Return list of marriage tuples (date, place) for an individual""" 181 | marriages = [] 182 | if not individual.is_individual(): 183 | raise ValueError("Operation only valid for elements with INDI tag") 184 | # Get and analyze families where individual is spouse. 185 | families = self.families(individual, "FAMS") 186 | for family in families: 187 | for family_data in family.children(): 188 | if family_data.tag() == "MARR": 189 | for marriage_data in family_data.children(): 190 | date = '' 191 | place = '' 192 | if marriage_data.tag() == "DATE": 193 | date = marriage_data.value() 194 | if marriage_data.tag() == "PLAC": 195 | place = marriage_data.value() 196 | marriages.append((date, place)) 197 | return marriages 198 | 199 | def marriage_years(self, individual): 200 | """Return list of marriage years (as int) for an individual""" 201 | dates = [] 202 | if not individual.is_individual(): 203 | raise ValueError("Operation only valid for elements with INDI tag") 204 | # Get and analyze families where individual is spouse. 205 | families = self.families(individual, "FAMS") 206 | for family in families: 207 | for family_data in family.children(): 208 | if family_data.tag() == "MARR": 209 | for marriage_data in family_data.children(): 210 | if marriage_data.tag() == "DATE": 211 | date = marriage_data.value().split()[-1] 212 | try: 213 | dates.append(int(date)) 214 | except ValueError: 215 | pass 216 | return dates 217 | 218 | def marriage_year_match(self, individual, year): 219 | """Check if one of the marriage years of an individual matches the supplied year. Year is an integer.""" 220 | years = self.marriage_years(individual) 221 | return year in years 222 | 223 | def marriage_range_match(self, individual, year1, year2): 224 | """Check if one of the marriage year of an individual is in a given range. Years are integers.""" 225 | years = self.marriage_years(individual) 226 | for year in years: 227 | if year1 <= year <= year2: 228 | return True 229 | return False 230 | 231 | def families(self, individual, family_type="FAMS"): 232 | """Return family elements listed for an individual 233 | 234 | family_type can be FAMS (families where the individual is a spouse) or 235 | FAMC (families where the individual is a child). If a value is not 236 | provided, FAMS is default value. 237 | """ 238 | if not individual.is_individual(): 239 | raise ValueError("Operation only valid for elements with INDI tag.") 240 | families = [] 241 | element_dict = self.element_dict() 242 | for child in individual.children(): 243 | is_family = (child.tag() == family_type and 244 | child.value() in element_dict and 245 | element_dict[child.value()].is_family()) 246 | if is_family: 247 | families.append(element_dict[child.value()]) 248 | return families 249 | 250 | def get_ancestors(self, individual, anc_type="ALL"): 251 | """Return elements corresponding to ancestors of an individual 252 | 253 | Optional anc_type. Default "ALL" returns all ancestors, "NAT" can be 254 | used to specify only natural (genetic) ancestors. 255 | """ 256 | if not individual.is_individual(): 257 | raise ValueError("Operation only valid for elements with INDI tag.") 258 | parents = self.get_parents(individual, anc_type) 259 | ancestors = parents 260 | for parent in parents: 261 | ancestors = ancestors + self.get_ancestors(parent) 262 | return ancestors 263 | 264 | def get_parents(self, individual, parent_type="ALL"): 265 | """Return elements corresponding to parents of an individual 266 | 267 | Optional parent_type. Default "ALL" returns all parents. "NAT" can be 268 | used to specify only natural (genetic) parents. 269 | """ 270 | if not individual.is_individual(): 271 | raise ValueError("Operation only valid for elements with INDI tag.") 272 | parents = [] 273 | families = self.families(individual, "FAMC") 274 | for family in families: 275 | if parent_type == "NAT": 276 | for family_member in family.children(): 277 | if family_member.tag() == "CHIL" and family_member.value() == individual.pointer(): 278 | for child in family_member.children(): 279 | if child.value() == "Natural": 280 | if child.tag() == "_MREL": 281 | parents = (parents + 282 | self.get_family_members(family, "WIFE")) 283 | elif child.tag() == "_FREL": 284 | parents = (parents + 285 | self.get_family_members(family, "HUSB")) 286 | else: 287 | parents = parents + self.get_family_members(family, "PARENTS") 288 | return parents 289 | 290 | def find_path_to_anc(self, desc, anc, path=None): 291 | """Return path from descendant to ancestor""" 292 | if not desc.is_individual() and anc.is_individual(): 293 | raise ValueError("Operation only valid for elements with IND tag.") 294 | if not path: 295 | path = [desc] 296 | if path[-1].pointer() == anc.pointer(): 297 | return path 298 | else: 299 | parents = self.get_parents(desc, "NAT") 300 | for par in parents: 301 | potential_path = self.find_path_to_anc(par, anc, path + [par]) 302 | if potential_path: 303 | return potential_path 304 | return None 305 | 306 | def get_family_members(self, family, mem_type="ALL"): 307 | """Return array of family members: individual, spouse, and children 308 | 309 | Optional argument `mem_type` can be used to return specific subsets. 310 | "ALL": Default, return all members of the family 311 | "PARENTS": Return individuals with "HUSB" and "WIFE" tags (parents) 312 | "HUSB": Return individuals with "HUSB" tags (father) 313 | "WIFE": Return individuals with "WIFE" tags (mother) 314 | "CHIL": Return individuals with "CHIL" tags (children) 315 | """ 316 | if not family.is_family(): 317 | raise ValueError("Operation only valid for elements with FAM tag.") 318 | family_members = [] 319 | element_dict = self.element_dict() 320 | for elem in family.children(): 321 | # Default is ALL 322 | is_family = (elem.tag() == "HUSB" or 323 | elem.tag() == "WIFE" or 324 | elem.tag() == "CHIL") 325 | if mem_type == "PARENTS": 326 | is_family = (elem.tag() == "HUSB" or 327 | elem.tag() == "WIFE") 328 | elif mem_type == "HUSB": 329 | is_family = (elem.tag() == "HUSB") 330 | elif mem_type == "WIFE": 331 | is_family = (elem.tag() == "WIFE") 332 | elif mem_type == "CHIL": 333 | is_family = (elem.tag() == "CHIL") 334 | if is_family and elem.value() in element_dict: 335 | family_members.append(element_dict[elem.value()]) 336 | return family_members 337 | 338 | # Other methods 339 | 340 | def print_gedcom(self): 341 | """Write GEDCOM data to stdout""" 342 | from sys import stdout 343 | self.save_gedcom(stdout) 344 | 345 | def save_gedcom(self, open_file): 346 | """Save GEDCOM data to a file""" 347 | if version_info[0] >= 3: 348 | open_file.write(self.root().get_individual()) 349 | else: 350 | open_file.write(self.root().get_individual().encode('utf-8')) 351 | 352 | 353 | class GedcomParseError(Exception): 354 | """Exception raised when a GEDCOM parsing error occurs""" 355 | 356 | def __init__(self, value): 357 | self.value = value 358 | 359 | def __str__(self): 360 | return repr(self.value) 361 | 362 | 363 | class Element: 364 | """GEDCOM element 365 | 366 | Each line in a GEDCOM file is an element with the format 367 | 368 | level [pointer] tag [value] 369 | 370 | where level and tag are required, and pointer and value are 371 | optional. Elements are arranged hierarchically according to their 372 | level, and elements with a level of zero are at the top level. 373 | Elements with a level greater than zero are children of their 374 | parent. 375 | 376 | A pointer has the format @pname@, where pname is any sequence of 377 | characters and numbers. The pointer identifies the object being 378 | pointed to, so that any pointer included as the value of any 379 | element points back to the original object. For example, an 380 | element may have a FAMS tag whose value is @F1@, meaning that this 381 | element points to the family record in which the associated person 382 | is a spouse. Likewise, an element with a tag of FAMC has a value 383 | that points to a family record in which the associated person is a 384 | child. 385 | 386 | See a GEDCOM file for examples of tags and their values. 387 | """ 388 | 389 | def __init__(self, level, pointer, tag, value, crlf="\n", multi_line=True): 390 | """Initialize an element 391 | 392 | You must include a level, pointer, tag, and value. 393 | Normally initialized by the GEDCOM parser, not by a user. 394 | """ 395 | # basic element info 396 | self.__level = level 397 | self.__pointer = pointer 398 | self.__tag = tag 399 | self.__value = value 400 | self.__crlf = crlf 401 | # structuring 402 | self.__children = [] 403 | self.__parent = None 404 | if multi_line: 405 | self.set_multi_line_value(value) 406 | 407 | def level(self): 408 | """Return the level of this element""" 409 | return self.__level 410 | 411 | def pointer(self): 412 | """Return the pointer of this element""" 413 | return self.__pointer 414 | 415 | def tag(self): 416 | """Return the tag of this element""" 417 | return self.__tag 418 | 419 | def value(self): 420 | """Return the value of this element""" 421 | return self.__value 422 | 423 | def set_value(self, value): 424 | """Set the value of this element""" 425 | self.__value = value 426 | 427 | def multi_line_value(self): 428 | """Return the value of this element including continuations""" 429 | result = self.value() 430 | last_crlf = self.__crlf 431 | for e in self.children(): 432 | tag = e.tag() 433 | if tag == 'CONC': 434 | result += e.value() 435 | last_crlf = e.__crlf 436 | elif tag == 'CONT': 437 | result += last_crlf + e.value() 438 | last_crlf = e.__crlf 439 | return result 440 | 441 | def __avail_chars(self): 442 | n = len(self.__unicode__()) 443 | if n > 255: 444 | return 0 445 | return 255 - n 446 | 447 | def __line_length(self, string): 448 | total = len(string) 449 | avail = self.__avail_chars() 450 | if total <= avail: 451 | return total 452 | 453 | spaces = 0 454 | while spaces < avail and string[avail - spaces - 1] == ' ': 455 | spaces = spaces + 1 456 | if spaces == avail: 457 | return avail 458 | return avail - spaces 459 | 460 | def __set_bounded_value(self, value): 461 | n = self.__line_length(value) 462 | self.set_value(value[:n]) 463 | return n 464 | 465 | def __add_bounded_child(self, tag, value): 466 | c = self.new_child(tag) 467 | return c.__set_bounded_value(value) 468 | 469 | def __add_concatenation(self, string): 470 | index = 0 471 | size = len(string) 472 | while index < size: 473 | index = index + self.__add_bounded_child('CONC', string[index:]) 474 | 475 | def set_multi_line_value(self, value): 476 | """Set the value of this element, adding continuation lines as necessary""" 477 | self.set_value('') 478 | self.children()[:] = [child for child in self.children() if child.tag() not in ('CONC', 'CONT')] 479 | 480 | lines = value.splitlines() 481 | if lines: 482 | line = lines.pop(0) 483 | n = self.__set_bounded_value(line) 484 | self.__add_concatenation(line[n:]) 485 | 486 | for line in lines: 487 | n = self.__add_bounded_child('CONT', line) 488 | self.__add_concatenation(line[n:]) 489 | 490 | def children(self): 491 | """Return the child elements of this element""" 492 | return self.__children 493 | 494 | def parent(self): 495 | """Return the parent element of this element""" 496 | return self.__parent 497 | 498 | def new_child(self, tag, pointer='', value=''): 499 | """Create and return a new child element of this element""" 500 | child = Element(self.level() + 1, pointer, tag, value, self.__crlf) 501 | self.add_child(child) 502 | return child 503 | 504 | def add_child(self, element): 505 | """Add a child element to this element""" 506 | self.children().append(element) 507 | element.add_parent(self) 508 | 509 | def add_parent(self, element): 510 | """Add a parent element to this element 511 | 512 | There's usually no need to call this method manually, 513 | add_child() calls it automatically. 514 | """ 515 | self.__parent = element 516 | 517 | def is_individual(self): 518 | """Check if this element is an individual""" 519 | return self.tag() == "INDI" 520 | 521 | def is_family(self): 522 | """Check if this element is a family""" 523 | return self.tag() == "FAM" 524 | 525 | def is_file(self): 526 | """Check if this element is a file""" 527 | return self.tag() == "FILE" 528 | 529 | def is_object(self): 530 | """Check if this element is an object""" 531 | return self.tag() == "OBJE" 532 | 533 | # criteria matching 534 | 535 | def criteria_match(self, criteria): 536 | """Check in this element matches all of the given criteria 537 | 538 | `criteria` is a colon-separated list, where each item in the 539 | list has the form [name]=[value]. The following criteria are supported: 540 | 541 | surname=[name] 542 | Match a person with [name] in any part of the surname. 543 | name=[name] 544 | Match a person with [name] in any part of the given name. 545 | birth=[year] 546 | Match a person whose birth year is a four-digit [year]. 547 | birthrange=[year1-year2] 548 | Match a person whose birth year is in the range of years from 549 | [year1] to [year2], including both [year1] and [year2]. 550 | death=[year] 551 | deathrange=[year1-year2] 552 | """ 553 | 554 | # error checking on the criteria 555 | try: 556 | for criterion in criteria.split(':'): 557 | key, value = criterion.split('=') 558 | except: 559 | return False 560 | match = True 561 | for criterion in criteria.split(':'): 562 | key, value = criterion.split('=') 563 | if key == "surname" and not self.surname_match(value): 564 | match = False 565 | elif key == "name" and not self.given_match(value): 566 | match = False 567 | elif key == "birth": 568 | try: 569 | year = int(value) 570 | if not self.birth_year_match(year): 571 | match = False 572 | except: 573 | match = False 574 | elif key == "birthrange": 575 | try: 576 | from_year, to_year = value.split('-') 577 | from_year = int(from_year) 578 | to_year = int(to_year) 579 | if not self.birth_range_match(from_year, to_year): 580 | match = False 581 | except: 582 | match = False 583 | elif key == "death": 584 | try: 585 | year = int(value) 586 | if not self.death_year_match(year): 587 | match = False 588 | except: 589 | match = False 590 | elif key == "deathrange": 591 | try: 592 | from_year, to_year = value.split('-') 593 | from_year = int(from_year) 594 | to_year = int(to_year) 595 | if not self.death_range_match(from_year, to_year): 596 | match = False 597 | except: 598 | match = False 599 | 600 | return match 601 | 602 | def surname_match(self, name): 603 | """Match a string with the surname of an individual""" 604 | (first, last) = self.name() 605 | return last.find(name) >= 0 606 | 607 | def given_match(self, name): 608 | """Match a string with the given names of an individual""" 609 | (first, last) = self.name() 610 | return first.find(name) >= 0 611 | 612 | def birth_year_match(self, year): 613 | """Match the birth year of an individual. Year is an integer""" 614 | return self.birth_year() == year 615 | 616 | def birth_range_match(self, year1, year2): 617 | """Check if the birth year of an individual is in a given range. Years are integers""" 618 | year = self.birth_year() 619 | if year1 <= year <= year2: 620 | return True 621 | return False 622 | 623 | def death_year_match(self, year): 624 | """Match the death year of an individual. Year is an integer""" 625 | return self.death_year() == year 626 | 627 | def death_range_match(self, year1, year2): 628 | """Check if the death year of an individual is in a given range. Years are integers""" 629 | year = self.death_year() 630 | if year1 <= year <= year2: 631 | return True 632 | return False 633 | 634 | def name(self): 635 | """Return a person's names as a tuple: (first,last)""" 636 | first = "" 637 | last = "" 638 | if not self.is_individual(): 639 | return first, last 640 | for child in self.children(): 641 | if child.tag() == "NAME": 642 | # some older GEDCOM files don't use child tags but instead 643 | # place the name in the value of the NAME tag 644 | if child.value() != "": 645 | name = child.value().split('/') 646 | if len(name) > 0: 647 | first = name[0].strip() 648 | if len(name) > 1: 649 | last = name[1].strip() 650 | else: 651 | for childOfChild in child.children(): 652 | if childOfChild.tag() == "GIVN": 653 | first = childOfChild.value() 654 | if childOfChild.tag() == "SURN": 655 | last = childOfChild.value() 656 | return first, last 657 | 658 | def gender(self): 659 | """Return the gender of a person in string format""" 660 | gender = "" 661 | if not self.is_individual(): 662 | return gender 663 | for child in self.children(): 664 | if child.tag() == "SEX": 665 | gender = child.value() 666 | return gender 667 | 668 | def private(self): 669 | """Return if the person is marked private in boolean format""" 670 | private = False 671 | if not self.is_individual(): 672 | return private 673 | for child in self.children(): 674 | if child.tag() == "PRIV": 675 | private = child.value() 676 | if private == 'Y': 677 | private = True 678 | return private 679 | 680 | def birth(self): 681 | """Return the birth tuple of a person as (date,place)""" 682 | date = "" 683 | place = "" 684 | source = () 685 | if not self.is_individual(): 686 | return date, place, source 687 | for child in self.children(): 688 | if child.tag() == "BIRT": 689 | for childOfChild in child.children(): 690 | if childOfChild.tag() == "DATE": 691 | date = childOfChild.value() 692 | if childOfChild.tag() == "PLAC": 693 | place = childOfChild.value() 694 | if childOfChild.tag() == "SOUR": 695 | source = source + (childOfChild.value(),) 696 | return date, place, source 697 | 698 | def birth_year(self): 699 | """Return the birth year of a person in integer format""" 700 | date = "" 701 | if not self.is_individual(): 702 | return date 703 | for child in self.children(): 704 | if child.tag() == "BIRT": 705 | for childOfChild in child.children(): 706 | if childOfChild.tag() == "DATE": 707 | date_split = childOfChild.value().split() 708 | date = date_split[len(date_split) - 1] 709 | if date == "": 710 | return -1 711 | try: 712 | return int(date) 713 | except: 714 | return -1 715 | 716 | def death(self): 717 | """Return the death tuple of a person as (date,place)""" 718 | date = "" 719 | place = "" 720 | source = () 721 | if not self.is_individual(): 722 | return date, place 723 | for child in self.children(): 724 | if child.tag() == "DEAT": 725 | for childOfChild in child.children(): 726 | if childOfChild.tag() == "DATE": 727 | date = childOfChild.value() 728 | if childOfChild.tag() == "PLAC": 729 | place = childOfChild.value() 730 | if childOfChild.tag() == "SOUR": 731 | source = source + (childOfChild.value(),) 732 | return date, place, source 733 | 734 | def death_year(self): 735 | """Return the death year of a person in integer format""" 736 | date = "" 737 | if not self.is_individual(): 738 | return date 739 | for child in self.children(): 740 | if child.tag() == "DEAT": 741 | for childOfChild in child.children(): 742 | if childOfChild.tag() == "DATE": 743 | date_split = childOfChild.value().split() 744 | date = date_split[len(date_split) - 1] 745 | if date == "": 746 | return -1 747 | try: 748 | return int(date) 749 | except: 750 | return -1 751 | 752 | def burial(self): 753 | """Return the burial tuple of a person as (date,place)""" 754 | date = "" 755 | place = "" 756 | source = () 757 | if not self.is_individual(): 758 | return date, place 759 | for child in self.children(): 760 | if child.tag() == "BURI": 761 | for childOfChild in child.children(): 762 | if childOfChild.tag() == "DATE": 763 | date = childOfChild.value() 764 | if childOfChild.tag() == "PLAC": 765 | place = childOfChild.value() 766 | if childOfChild.tag() == "SOUR": 767 | source = source + (childOfChild.value(),) 768 | return date, place, source 769 | 770 | def census(self): 771 | """Return list of census tuples (date, place) for an individual""" 772 | census = [] 773 | if not self.is_individual(): 774 | raise ValueError("Operation only valid for elements with INDI tag") 775 | for child in self.children(): 776 | if child.tag() == "CENS": 777 | date = '' 778 | place = '' 779 | source = '' 780 | for childOfChild in child.children(): 781 | if childOfChild.tag() == "DATE": 782 | date = childOfChild.value() 783 | if childOfChild.tag() == "PLAC": 784 | place = childOfChild.value() 785 | if childOfChild.tag() == "SOUR": 786 | source = source + (childOfChild.value(),) 787 | census.append((date, place, source)) 788 | return census 789 | 790 | def last_updated(self): 791 | """Return the last updated date of a person as (date)""" 792 | date = "" 793 | if not self.is_individual(): 794 | return date 795 | for child in self.children(): 796 | if child.tag() == "CHAN": 797 | for childOfChild in child.children(): 798 | if childOfChild.tag() == "DATE": 799 | date = childOfChild.value() 800 | return date 801 | 802 | def occupation(self): 803 | """Return the occupation of a person as (date)""" 804 | occupation = "" 805 | if not self.is_individual(): 806 | return occupation 807 | for child in self.children(): 808 | if child.tag() == "OCCU": 809 | occupation = child.value() 810 | return occupation 811 | 812 | def deceased(self): 813 | """Check if a person is deceased""" 814 | if not self.is_individual(): 815 | return False 816 | for child in self.children(): 817 | if child.tag() == "DEAT": 818 | return True 819 | return False 820 | 821 | def get_individual(self): 822 | """Return this element and all of its sub-elements""" 823 | result = self.__unicode__() 824 | for child in self.children(): 825 | result += child.get_individual() 826 | return result 827 | 828 | def __str__(self): 829 | if version_info[0] >= 3: 830 | return self.__unicode__() 831 | else: 832 | return self.__unicode__().encode('utf-8') 833 | 834 | def __unicode__(self): 835 | """Format this element as its original string""" 836 | if self.level() < 0: 837 | return '' 838 | result = str(self.level()) 839 | if self.pointer() != "": 840 | result += ' ' + self.pointer() 841 | result += ' ' + self.tag() 842 | if self.value() != "": 843 | result += ' ' + self.value() 844 | result += self.__crlf 845 | return result 846 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name='python-gedcom', 5 | version='0.1.1dev', 6 | packages=['gedcom', ], 7 | license='GPLv2', 8 | package_dir={'': '.'}, 9 | description=open('README.md').readlines()[0].strip(), 10 | long_description=open('README.md').read(), 11 | maintainer='Madeleine Ball', 12 | maintainer_email='mpball@gmail.com', 13 | url='https://github.com/madprime/python-gedcom', 14 | ) 15 | --------------------------------------------------------------------------------