\n")
145 | self.__write_family(self.person.id, 0)
146 |
147 | def write_siblings(self):
148 | list_of_child_ids = []
149 | for parent_id in self.person.parent_id:
150 | for family in self.all_persons[parent_id].family:
151 | for child_id in family.child_id:
152 | if child_id not in list_of_child_ids:
153 | if child_id != self.person.id:
154 | list_of_child_ids.append(str(child_id))
155 | if len(list_of_child_ids) > 0:
156 | self.__fid.write("
Siblings
\n")
157 | self.__fid.write("
\n")
158 | for child_id in list_of_child_ids:
159 | self.__fid.write("
'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},c.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){var e=a.proxy(this.process,this);this.$body=a("body"),this.$scrollElement=a(a(c).is("body")?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",e),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.1",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b="offset",c=0;a.isWindow(this.$scrollElement[0])||(b="position",c=this.$scrollElement.scrollTop()),this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight();var d=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+c,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){d.offsets.push(this[0]),d.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.1",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})
7 | })}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.1",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=i?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a("body").height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
--------------------------------------------------------------------------------
/gedcomParser.py:
--------------------------------------------------------------------------------
1 | # Python GEDCOM Parser
2 | #
3 | # Copyright (C) 2018 Nicklas Reincke (contact at reynke.com)
4 | # Copyright (C) 2016 Andreas Oberritter
5 | # Copyright (C) 2012 Madeleine Price Ball
6 | # Copyright (C) 2005 Daniel Zappala (zappala at cs.byu.edu)
7 | # Copyright (C) 2005 Brigham Young University
8 | #
9 | # This program is free software; you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation; either version 2 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License along
20 | # with this program; if not, write to the Free Software Foundation, Inc.,
21 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 | #
23 | # Further information about the license: http://www.gnu.org/licenses/gpl-2.0.html
24 |
25 | import re as regex
26 | from sys import version_info
27 | from datetime import datetime
28 |
29 | __all__ = ["Gedcom", "Element", "GedcomParseError"]
30 |
31 | # Relationship to a mother.
32 | GEDCOM_PROGRAM_DEFINED_TAG_MREL = "_MREL"
33 |
34 | # Relationship to a father.
35 | GEDCOM_PROGRAM_DEFINED_TAG_FREL = "_FREL"
36 |
37 | GEDCOM_TAG_NOTES = "NOTE"
38 |
39 | # The event of entering into life.
40 | GEDCOM_TAG_BIRTH = "BIRT"
41 |
42 | # The event of the proper disposing of the mortal remains of a deceased person.
43 | GEDCOM_TAG_BURIAL = "BURI"
44 |
45 | # The event of the periodic count of the population for a designated locality, such as a national or state Census.
46 | GEDCOM_TAG_CENSUS = "CENS"
47 |
48 | # Indicates a change, correction, or modification. Typically used in connection
49 | # with a DATE to specify when a change in information occurred.
50 | GEDCOM_TAG_CHANGE = "CHAN"
51 |
52 | # The natural, adopted, or sealed (LDS) child of a father and a mother.
53 | GEDCOM_TAG_CHILD = "CHIL"
54 |
55 | # An indicator that additional data belongs to the superior value. The information from the CONC value is to be
56 | # connected to the value of the superior preceding line without a space and without a carriage return and/or
57 | # new line character. Values that are split for a CONC tag must always be split at a non-space. If the value is
58 | # split on a space the space will be lost when concatenation takes place. This is because of the treatment that
59 | # spaces get as a GEDCOM delimiter, many GEDCOM values are trimmed of trailing spaces and some systems look for
60 | # the first non-space starting after the tag to determine the beginning of the value.
61 | GEDCOM_TAG_CONCATENATION = "CONC"
62 |
63 | # An indicator that additional data belongs to the superior value. The information from the CONT value is to be
64 | # connected to the value of the superior preceding line with a carriage return and/or new line character.
65 | # Leading spaces could be important to the formatting of the resultant text. When importing values from CONT lines
66 | # the reader should assume only one delimiter character following the CONT tag. Assume that the rest of the leading
67 | # spaces are to be a part of the value.
68 | GEDCOM_TAG_CONTINUED = "CONT"
69 |
70 | # The time of an event in a calendar format.
71 | GEDCOM_TAG_DATE = "DATE"
72 |
73 | # The event when mortal life terminates.
74 | GEDCOM_TAG_DEATH = "DEAT"
75 |
76 | # Identifies a legal, common law, or other customary relationship of man and woman and their children,
77 | # if any, or a family created by virtue of the birth of a child to its biological father and mother.
78 | GEDCOM_TAG_FAMILY = "FAM"
79 |
80 | # Identifies the family in which an individual appears as a child.
81 | GEDCOM_TAG_FAMILY_CHILD = "FAMC"
82 |
83 | # Identifies the family in which an individual appears as a spouse.
84 | GEDCOM_TAG_FAMILY_SPOUSE = "FAMS"
85 |
86 | # An information storage place that is ordered and arranged for preservation and reference.
87 | GEDCOM_TAG_FILE = "FILE"
88 |
89 | # A given or earned name used for official identification of a person.
90 | GEDCOM_TAG_GIVEN_NAME = "GIVN"
91 |
92 | # An individual in the family role of a married man or father.
93 | GEDCOM_TAG_HUSBAND = "HUSB"
94 |
95 | # A person.
96 | GEDCOM_TAG_INDIVIDUAL = "INDI"
97 |
98 | # A legal, common-law, or customary event of creating a family unit of a man and a woman as husband and wife.
99 | GEDCOM_TAG_MARRIAGE = "MARR"
100 |
101 | # A word or combination of words used to help identify an individual, title, or other item.
102 | # More than one NAME line should be used for people who were known by multiple names.
103 | GEDCOM_TAG_NAME = "NAME"
104 |
105 | # Pertaining to a grouping of attributes used in describing something. Usually referring to the data required
106 | # to represent a multimedia object, such an audio recording, a photograph of a person, or an image of a document.
107 | GEDCOM_TAG_OBJECT = "OBJE"
108 |
109 | # The type of work or profession of an individual.
110 | GEDCOM_TAG_OCCUPATION = "OCCU"
111 |
112 | # A jurisdictional name to identify the place or location of an event.
113 | GEDCOM_TAG_PLACE = "PLAC"
114 |
115 | # Flag for private address or event.
116 | GEDCOM_TAG_PRIVATE = "PRIV"
117 |
118 | # Indicates the sex of an individual--male or female.
119 | GEDCOM_TAG_SEX = "SEX"
120 |
121 | # The initial or original material from which information was obtained.
122 | GEDCOM_TAG_SOURCE = "SOUR"
123 |
124 | # A family name passed on or used by members of a family.
125 | GEDCOM_TAG_SURNAME = "SURN"
126 | GEDCOM_TAG_NICKNAME = "NICK"
127 |
128 | # An individual in the role as a mother and/or married woman.
129 | GEDCOM_TAG_WIFE = "WIFE"
130 | GEDCOM_TAG_TITLE = "TITL"
131 | GEDCOM_TAG_PUBLICATION = "PUBL"
132 |
133 |
134 | class Gedcom:
135 | """Parses and manipulates GEDCOM 5.5 format data
136 |
137 | For documentation of the GEDCOM 5.5 format, see:
138 | http://homepages.rootsweb.ancestry.com/~pmcbride/gedcom/55gctoc.htm
139 |
140 | This parser reads and parses a GEDCOM file.
141 | Elements may be accessed via:
142 | - a list (all elements, default order is same as in file)
143 | - a dict (only elements with pointers, which are the keys)
144 | """
145 |
146 | def __init__(self, file_path):
147 | """Initialize a GEDCOM data object. You must supply a GEDCOM file
148 | :type file_path: str
149 | """
150 | self.__element_list = []
151 | self.__element_dictionary = {}
152 | self.invalidate_cache()
153 | self.__root_element = Element(-1, "", "ROOT", "")
154 | self.__parse(file_path)
155 |
156 | def invalidate_cache(self):
157 | """Cause get_element_list() and get_element_dictionary() to return updated data
158 |
159 | The update gets deferred until each of the methods actually gets called.
160 | """
161 | self.__element_list = []
162 | self.__element_dictionary = {}
163 |
164 | def get_element_list(self):
165 | """Return a list of all the elements in the GEDCOM file
166 |
167 | By default elements are in the same order as they appeared in the file.
168 |
169 | This list gets generated on-the-fly, but gets cached. If the database
170 | was modified, you should call invalidate_cache() once to let this
171 | method return updated data.
172 |
173 | Consider using `get_root_element()` or `get_root_child_elements()` to access
174 | the hierarchical GEDCOM tree, unless you rarely modify the database.
175 |
176 | :rtype: list of Element
177 | """
178 | if not self.__element_list:
179 | for element in self.get_root_child_elements():
180 | self.__build_list(element, self.__element_list)
181 | return self.__element_list
182 |
183 | def get_element_dictionary(self):
184 | """Return a dictionary of elements from the GEDCOM file
185 |
186 | Only elements identified by a pointer are listed in the dictionary.
187 | The keys for the dictionary are the pointers.
188 |
189 | This dictionary gets generated on-the-fly, but gets cached. If the
190 | database was modified, you should call invalidate_cache() once to let
191 | this method return updated data.
192 |
193 | :rtype: dict of Element
194 | """
195 | if not self.__element_dictionary:
196 | self.__element_dictionary = {
197 | element.get_pointer(): element for element in self.get_root_child_elements() if element.get_pointer()
198 | }
199 |
200 | return self.__element_dictionary
201 |
202 | def get_root_element(self):
203 | """Returns a virtual root element containing all logical records as children
204 |
205 | When printed, this element converts to an empty string.
206 |
207 | :rtype: Element
208 | """
209 | return self.__root_element
210 |
211 | def get_root_child_elements(self):
212 | """Return a list of logical records in the GEDCOM file
213 |
214 | By default, elements are in the same order as they appeared in the file.
215 |
216 | :rtype: list of Element
217 | """
218 | return self.get_root_element().get_child_elements()
219 |
220 | # Private methods
221 |
222 | def __parse(self, file_path):
223 | """Open and parse file path as GEDCOM 5.5 formatted data
224 | :type file_path: str
225 | """
226 | gedcom_file = open(file_path, 'rb')
227 | line_number = 1
228 | last_element = self.__root_element
229 | for line in gedcom_file:
230 | try:
231 | last_element = self.__parse_line(line_number, line.decode('utf-8'), last_element)
232 | except:
233 | print(line_number)
234 | print(line)
235 | line_number += 1
236 |
237 | @staticmethod
238 | def __parse_line(line_number, line, last_element):
239 | """Parse a line from a GEDCOM 5.5 formatted document
240 |
241 | Each line should have the following (bracketed items optional):
242 | level + ' ' + [pointer + ' ' +] tag + [' ' + line_value]
243 |
244 | :type line_number: int
245 | :type line: str
246 | :type last_element: Element
247 | :rtype: Element
248 | """
249 |
250 | # Level must start with non-negative int, no leading zeros.
251 | level_regex = '^(0|[1-9]+[0-9]*) '
252 |
253 | # Pointer optional, if it exists it must be flanked by `@`
254 | pointer_regex = '(@[^@]+@ |)'
255 |
256 | # Tag must be alphanumeric string
257 | tag_regex = '([A-Za-z0-9_]+)'
258 |
259 | # Value optional, consists of anything after a space to end of line
260 | value_regex = '( [^\n\r]*|)'
261 |
262 | # End of line defined by `\n` or `\r`
263 | end_of_line_regex = '([\r\n]{1,2})'
264 |
265 | # Complete regex
266 | gedcom_line_regex = level_regex + pointer_regex + tag_regex + value_regex + end_of_line_regex
267 | regex_match = regex.match(gedcom_line_regex, line)
268 |
269 | if regex_match is None:
270 | error_message = ("Line `%d` of document violates GEDCOM format 5.5" % line_number +
271 | "\nSee: https://chronoplexsoftware.com/gedcomvalidator/gedcom/gedcom-5.5.pdf")
272 | raise SyntaxError(error_message)
273 |
274 | line_parts = regex_match.groups()
275 |
276 | level = int(line_parts[0])
277 | pointer = line_parts[1].rstrip(' ')
278 | tag = line_parts[2]
279 | value = line_parts[3][1:]
280 | crlf = line_parts[4]
281 |
282 | # Check level: should never be more than one higher than previous line.
283 | if level > last_element.get_level() + 1:
284 | error_message = ("Line %d of document violates GEDCOM format 5.5" % line_number +
285 | "\nLines must be no more than one level higher than previous line." +
286 | "\nSee: https://chronoplexsoftware.com/gedcomvalidator/gedcom/gedcom-5.5.pdf")
287 | raise SyntaxError(error_message)
288 |
289 | # Create element. Store in list and dict, create children and parents.
290 | element = Element(level, pointer, tag, value, crlf, multi_line=False)
291 |
292 | # Start with last element as parent, back up if necessary.
293 | parent_element = last_element
294 |
295 | while parent_element.get_level() > level - 1:
296 | parent_element = parent_element.get_parent_element()
297 |
298 | # Add child to parent & parent to child.
299 | parent_element.add_child_element(element)
300 |
301 | return element
302 |
303 | def __build_list(self, element, element_list):
304 | """Recursively add elements to a list containing elements
305 | :type element: Element
306 | :type element_list: list of Element
307 | """
308 | element_list.append(element)
309 | for child in element.get_child_elements():
310 | self.__build_list(child, element_list)
311 |
312 | # Methods for analyzing individuals and relationships between individuals
313 |
314 | def get_marriages(self, individual):
315 | """Return list of marriage tuples (date, place) for an individual
316 | :type individual: Element
317 | :rtype: tuple
318 | """
319 | marriages = []
320 | if not individual.is_individual():
321 | raise ValueError("Operation only valid for elements with %s tag" % GEDCOM_TAG_INDIVIDUAL)
322 | # Get and analyze families where individual is spouse.
323 | families = self.get_families(individual, GEDCOM_TAG_FAMILY_SPOUSE)
324 | for family in families:
325 | for family_data in family.get_child_elements():
326 | if family_data.get_tag() == GEDCOM_TAG_MARRIAGE:
327 | for marriage_data in family_data.get_child_elements():
328 | date = ''
329 | place = ''
330 | if marriage_data.get_tag() == GEDCOM_TAG_DATE:
331 | date = marriage_data.get_value()
332 | if marriage_data.get_tag() == GEDCOM_TAG_PLACE:
333 | place = marriage_data.get_value()
334 | marriages.append((date, place))
335 | return marriages
336 |
337 | def get_marriage_years(self, individual):
338 | """Return list of marriage years (as int) for an individual
339 | :type individual: Element
340 | :rtype: list of int
341 | """
342 | dates = []
343 | if not individual.is_individual():
344 | raise ValueError("Operation only valid for elements with %s tag" % GEDCOM_TAG_INDIVIDUAL)
345 | # Get and analyze families where individual is spouse.
346 | families = self.get_families(individual, GEDCOM_TAG_FAMILY_SPOUSE)
347 | for family in families:
348 | for child in family.get_child_elements():
349 | if child.get_tag() == GEDCOM_TAG_MARRIAGE:
350 | for childOfChild in child.get_child_elements():
351 | if childOfChild.get_tag() == GEDCOM_TAG_DATE:
352 | date = childOfChild.get_value().split()[-1]
353 | try:
354 | dates.append(int(date))
355 | except ValueError:
356 | pass
357 | return dates
358 |
359 | def marriage_year_match(self, individual, year):
360 | """Check if one of the marriage years of an individual matches the supplied year. Year is an integer.
361 | :type individual: Element
362 | :type year: int
363 | :rtype: bool
364 | """
365 | years = self.get_marriage_years(individual)
366 | return year in years
367 |
368 | def marriage_range_match(self, individual, from_year, to_year):
369 | """Check if one of the marriage years of an individual is in a given range. Years are integers.
370 | :type individual: Element
371 | :type from_year: int
372 | :type to_year: int
373 | :rtype: bool
374 | """
375 | years = self.get_marriage_years(individual)
376 | for year in years:
377 | if from_year <= year <= to_year:
378 | return True
379 | return False
380 |
381 | def get_families(self, individual, family_type=GEDCOM_TAG_FAMILY_SPOUSE):
382 | """Return family elements listed for an individual
383 |
384 | family_type can be `GEDCOM_TAG_FAMILY_SPOUSE` (families where the individual is a spouse) or
385 | `GEDCOM_TAG_FAMILY_CHILD` (families where the individual is a child). If a value is not
386 | provided, `GEDCOM_TAG_FAMILY_SPOUSE` is default value.
387 |
388 | :type individual: Element
389 | :type family_type: str
390 | :rtype: list of Element
391 | """
392 | if not individual.is_individual():
393 | raise ValueError("Operation only valid for elements with %s tag." % GEDCOM_TAG_INDIVIDUAL)
394 | families = []
395 | element_dictionary = self.get_element_dictionary()
396 | for child_element in individual.get_child_elements():
397 | is_family = (child_element.get_tag() == family_type and
398 | child_element.get_value() in element_dictionary and
399 | element_dictionary[child_element.get_value()].is_family())
400 | if is_family:
401 | families.append(element_dictionary[child_element.get_value()])
402 | return families
403 |
404 | def get_ancestors(self, individual, ancestor_type="ALL"):
405 | """Return elements corresponding to ancestors of an individual
406 |
407 | Optional `ancestor_type`. Default "ALL" returns all ancestors, "NAT" can be
408 | used to specify only natural (genetic) ancestors.
409 |
410 | :type individual: Element
411 | :type ancestor_type: str
412 | :rtype: list of Element
413 | """
414 | if not individual.is_individual():
415 | raise ValueError("Operation only valid for elements with %s tag." % GEDCOM_TAG_INDIVIDUAL)
416 | parents = self.get_parents(individual, ancestor_type)
417 | ancestors = []
418 | ancestors.extend(parents)
419 | for parent in parents:
420 | ancestors.extend(self.get_ancestors(parent))
421 | return ancestors
422 |
423 | def get_parents(self, individual, parent_type="ALL"):
424 | """Return elements corresponding to parents of an individual
425 |
426 | Optional parent_type. Default "ALL" returns all parents. "NAT" can be
427 | used to specify only natural (genetic) parents.
428 |
429 | :type individual: Element
430 | :type parent_type: str
431 | :rtype: list of Element
432 | """
433 | if not individual.is_individual():
434 | raise ValueError("Operation only valid for elements with %s tag." % GEDCOM_TAG_INDIVIDUAL)
435 | parents = []
436 | families = self.get_families(individual, GEDCOM_TAG_FAMILY_CHILD)
437 | for family in families:
438 | if parent_type == "NAT":
439 | for family_member in family.get_child_elements():
440 | if family_member.get_tag() == GEDCOM_TAG_CHILD and family_member.get_value() == individual.get_pointer():
441 | for child in family_member.get_child_elements():
442 | if child.get_value() == "Natural":
443 | if child.get_tag() == GEDCOM_PROGRAM_DEFINED_TAG_MREL:
444 | parents += self.get_family_members(family, GEDCOM_TAG_WIFE)
445 | elif child.get_tag() == GEDCOM_PROGRAM_DEFINED_TAG_FREL:
446 | parents += self.get_family_members(family, GEDCOM_TAG_HUSBAND)
447 | else:
448 | parents += self.get_family_members(family, "PARENTS")
449 | return parents
450 |
451 | def find_path_to_ancestor(self, descendant, ancestor, path=None):
452 | """Return path from descendant to ancestor
453 | :rtype: object
454 | """
455 | if not descendant.is_individual() and ancestor.is_individual():
456 | raise ValueError("Operation only valid for elements with %s tag." % GEDCOM_TAG_INDIVIDUAL)
457 | if not path:
458 | path = [descendant]
459 | if path[-1].get_pointer() == ancestor.get_pointer():
460 | return path
461 | else:
462 | parents = self.get_parents(descendant, "NAT")
463 | for parent in parents:
464 | potential_path = self.find_path_to_ancestor(parent, ancestor, path + [parent])
465 | if potential_path is not None:
466 | return potential_path
467 | return None
468 |
469 | def get_family_members(self, family, members_type="ALL"):
470 | """Return array of family members: individual, spouse, and children
471 |
472 | Optional argument `members_type` can be used to return specific subsets.
473 | "ALL": Default, return all members of the family
474 | "PARENTS": Return individuals with "HUSB" and "WIFE" tags (parents)
475 | "HUSB": Return individuals with "HUSB" tags (father)
476 | "WIFE": Return individuals with "WIFE" tags (mother)
477 | "CHIL": Return individuals with "CHIL" tags (children)
478 |
479 | :type family: Element
480 | :type members_type: str
481 | :rtype: list of Element
482 | """
483 | if not family.is_family():
484 | raise ValueError("Operation only valid for element with %s tag." % GEDCOM_TAG_FAMILY)
485 | family_members = []
486 | element_dictionary = self.get_element_dictionary()
487 | for child_element in family.get_child_elements():
488 | # Default is ALL
489 | is_family = (child_element.get_tag() == GEDCOM_TAG_HUSBAND or
490 | child_element.get_tag() == GEDCOM_TAG_WIFE or
491 | child_element.get_tag() == GEDCOM_TAG_CHILD)
492 | if members_type == "PARENTS":
493 | is_family = (child_element.get_tag() == GEDCOM_TAG_HUSBAND or
494 | child_element.get_tag() == GEDCOM_TAG_WIFE)
495 | elif members_type == "HUSB":
496 | is_family = child_element.get_tag() == GEDCOM_TAG_HUSBAND
497 | elif members_type == "WIFE":
498 | is_family = child_element.get_tag() == GEDCOM_TAG_WIFE
499 | elif members_type == "CHIL":
500 | is_family = child_element.get_tag() == GEDCOM_TAG_CHILD
501 | if is_family and child_element.get_value() in element_dictionary:
502 | family_members.append(element_dictionary[child_element.get_value()])
503 | return family_members
504 |
505 | # Other methods
506 |
507 | def print_gedcom(self):
508 | """Write GEDCOM data to stdout"""
509 | from sys import stdout
510 | self.save_gedcom(stdout)
511 |
512 | def save_gedcom(self, open_file):
513 | """Save GEDCOM data to a file
514 | :type open_file: file
515 | """
516 | if version_info[0] >= 3:
517 | open_file.write(self.get_root_element().get_individual())
518 | else:
519 | open_file.write(self.get_root_element().get_individual().encode('utf-8'))
520 |
521 |
522 | class GedcomParseError(Exception):
523 | """Exception raised when a GEDCOM parsing error occurs"""
524 |
525 | def __init__(self, value):
526 | self.value = value
527 |
528 | def __str__(self):
529 | return repr(self.value)
530 |
531 |
532 | class Element:
533 | """GEDCOM element
534 |
535 | Each line in a GEDCOM file is an element with the format
536 |
537 | level [pointer] tag [value]
538 |
539 | where level and tag are required, and pointer and value are
540 | optional. Elements are arranged hierarchically according to their
541 | level, and elements with a level of zero are at the top level.
542 | Elements with a level greater than zero are children of their
543 | parent.
544 |
545 | A pointer has the format @pname@, where pname is any sequence of
546 | characters and numbers. The pointer identifies the object being
547 | pointed to, so that any pointer included as the value of any
548 | element points back to the original object. For example, an
549 | element may have a FAMS tag whose value is @F1@, meaning that this
550 | element points to the family record in which the associated person
551 | is a spouse. Likewise, an element with a tag of FAMC has a value
552 | that points to a family record in which the associated person is a
553 | child.
554 |
555 | See a GEDCOM file for examples of tags and their values.
556 | """
557 |
558 | def __init__(self, level, pointer, tag, value, crlf="\n", multi_line=True):
559 | """Initialize an element
560 |
561 | You must include a level, a pointer, a tag, and a value.
562 | Normally initialized by the GEDCOM parser, not by a user.
563 |
564 | :type level: int
565 | :type pointer: str
566 | :type tag: str
567 | :type value: str
568 | :type crlf: str
569 | :type multi_line: bool
570 | """
571 |
572 | # basic element info
573 | self.__level = level
574 | self.__pointer = pointer
575 | self.__tag = tag
576 | self.__value = value
577 | self.__crlf = crlf
578 |
579 | # structuring
580 | self.__children = []
581 | self.__parent = None
582 |
583 | if multi_line:
584 | self.set_multi_line_value(value)
585 |
586 | def get_level(self):
587 | """Return the level of this element
588 | :rtype: int
589 | """
590 | return self.__level
591 |
592 | def get_pointer(self):
593 | """Return the pointer of this element
594 | :rtype: str
595 | """
596 | return self.__pointer
597 |
598 | def get_tag(self):
599 | """Return the tag of this element
600 | :rtype: str
601 | """
602 | return self.__tag
603 |
604 | def get_value(self):
605 | """Return the value of this element
606 | :rtype: str
607 | """
608 | return self.__value
609 |
610 | def set_value(self, value):
611 | """Set the value of this element
612 | :type value: str
613 | """
614 | self.__value = value
615 |
616 | def get_multi_line_value(self):
617 | """Return the value of this element including continuations
618 | :rtype: str
619 | """
620 | result = self.get_value()
621 | last_crlf = self.__crlf
622 | for element in self.get_child_elements():
623 | tag = element.get_tag()
624 | if tag == GEDCOM_TAG_CONCATENATION:
625 | result += element.get_value()
626 | last_crlf = element.__crlf
627 | elif tag == GEDCOM_TAG_CONTINUED:
628 | result += last_crlf + element.get_value()
629 | last_crlf = element.__crlf
630 | return result
631 |
632 | def __available_characters(self):
633 | """Get the number of available characters of the elements original string
634 | :rtype: int
635 | """
636 | element_characters = len(self.__unicode__())
637 | return 0 if element_characters > 255 else 255 - element_characters
638 |
639 | def __line_length(self, line):
640 | """@TODO Write docs.
641 | :type line: str
642 | :rtype: int
643 | """
644 | total_characters = len(line)
645 | available_characters = self.__available_characters()
646 | if total_characters <= available_characters:
647 | return total_characters
648 | spaces = 0
649 | while spaces < available_characters and line[available_characters - spaces - 1] == ' ':
650 | spaces += 1
651 | if spaces == available_characters:
652 | return available_characters
653 | return available_characters - spaces
654 |
655 | def __set_bounded_value(self, value):
656 | """@TODO Write docs.
657 | :type value: str
658 | :rtype: int
659 | """
660 | line_length = self.__line_length(value)
661 | self.set_value(value[:line_length])
662 | return line_length
663 |
664 | def __add_bounded_child(self, tag, value):
665 | """@TODO Write docs.
666 | :type tag: str
667 | :type value: str
668 | :rtype: int
669 | """
670 | child = self.new_child_element(tag)
671 | return child.__set_bounded_value(value)
672 |
673 | def __add_concatenation(self, string):
674 | """@TODO Write docs.
675 | :rtype: str
676 | """
677 | index = 0
678 | size = len(string)
679 | while index < size:
680 | index += self.__add_bounded_child(GEDCOM_TAG_CONCATENATION, string[index:])
681 |
682 | def set_multi_line_value(self, value):
683 | """Set the value of this element, adding continuation lines as necessary
684 | :type value: str
685 | """
686 | self.set_value('')
687 | self.get_child_elements()[:] = [child for child in self.get_child_elements() if
688 | child.get_tag() not in (GEDCOM_TAG_CONCATENATION, GEDCOM_TAG_CONTINUED)]
689 |
690 | lines = value.splitlines()
691 | if lines:
692 | line = lines.pop(0)
693 | n = self.__set_bounded_value(line)
694 | self.__add_concatenation(line[n:])
695 |
696 | for line in lines:
697 | n = self.__add_bounded_child(GEDCOM_TAG_CONTINUED, line)
698 | self.__add_concatenation(line[n:])
699 |
700 | def get_child_elements(self):
701 | """Return the child elements of this element
702 | :rtype: list of Element
703 | """
704 | return self.__children
705 |
706 | def get_parent_element(self):
707 | """Return the parent element of this element
708 | :rtype: Element
709 | """
710 | return self.__parent
711 |
712 | def new_child_element(self, tag, pointer="", value=""):
713 | """Create and return a new child element of this element
714 |
715 | :type tag: str
716 | :type pointer: str
717 | :type value: str
718 | :rtype: Element
719 | """
720 | child_element = Element(self.get_level() + 1, pointer, tag, value, self.__crlf)
721 | self.add_child_element(child_element)
722 | return child_element
723 |
724 | def add_child_element(self, element):
725 | """Add a child element to this element
726 |
727 | :type element: Element
728 | """
729 | self.get_child_elements().append(element)
730 | element.set_parent_element(self)
731 |
732 | def set_parent_element(self, element):
733 | """Add a parent element to this element
734 |
735 | There's usually no need to call this method manually,
736 | add_child_element() calls it automatically.
737 |
738 | :type element: Element
739 | """
740 | self.__parent = element
741 |
742 | def is_individual(self):
743 | """Check if this element is an individual
744 | :rtype: bool
745 | """
746 | return self.get_tag() == GEDCOM_TAG_INDIVIDUAL
747 |
748 | def is_source(self):
749 | """Check if this element is a source
750 | :rtype: bool
751 | """
752 | return self.get_tag() == GEDCOM_TAG_SOURCE
753 |
754 | def is_family(self):
755 | """Check if this element is a family
756 | :rtype: bool
757 | """
758 | return self.get_tag() == GEDCOM_TAG_FAMILY
759 |
760 | def is_file(self):
761 | """Check if this element is a file
762 | :rtype: bool
763 | """
764 | return self.get_tag() == GEDCOM_TAG_FILE
765 |
766 | def is_object(self):
767 | """Check if this element is an object
768 | :rtype: bool
769 | """
770 | return self.get_tag() == GEDCOM_TAG_OBJECT
771 |
772 | # criteria matching
773 |
774 | def criteria_match(self, criteria):
775 | """Check in this element matches all of the given criteria
776 |
777 | `criteria` is a colon-separated list, where each item in the
778 | list has the form [name]=[value]. The following criteria are supported:
779 |
780 | surname=[name]
781 | Match a person with [name] in any part of the surname.
782 | name=[name]
783 | Match a person with [name] in any part of the given name.
784 | birth=[year]
785 | Match a person whose birth year is a four-digit [year].
786 | birth_range=[from_year-to_year]
787 | Match a person whose birth year is in the range of years from
788 | [from_year] to [to_year], including both [from_year] and [to_year].
789 | death=[year]
790 | death_range=[from_year-to_year]
791 |
792 | :type criteria: str
793 | :rtype: bool
794 | """
795 |
796 | # error checking on the criteria
797 | try:
798 | for criterion in criteria.split(':'):
799 | key, value = criterion.split('=')
800 | except:
801 | return False
802 | match = True
803 | for criterion in criteria.split(':'):
804 | key, value = criterion.split('=')
805 | if key == "surname" and not self.surname_match(value):
806 | match = False
807 | elif key == "name" and not self.given_match(value):
808 | match = False
809 | elif key == "birth":
810 | try:
811 | year = int(value)
812 | if not self.birth_year_match(year):
813 | match = False
814 | except:
815 | match = False
816 | elif key == "birth_range":
817 | try:
818 | from_year, to_year = value.split('-')
819 | from_year = int(from_year)
820 | to_year = int(to_year)
821 | if not self.birth_range_match(from_year, to_year):
822 | match = False
823 | except:
824 | match = False
825 | elif key == "death":
826 | try:
827 | year = int(value)
828 | if not self.death_year_match(year):
829 | match = False
830 | except:
831 | match = False
832 | elif key == "death_range":
833 | try:
834 | from_year, to_year = value.split('-')
835 | from_year = int(from_year)
836 | to_year = int(to_year)
837 | if not self.death_range_match(from_year, to_year):
838 | match = False
839 | except:
840 | match = False
841 |
842 | return match
843 |
844 | def surname_match(self, name):
845 | """Match a string with the surname of an individual
846 | :type name: str
847 | :rtype: bool
848 | """
849 | (first, last) = self.get_name()
850 | return last.find(name) >= 0
851 |
852 | def given_match(self, name):
853 | """Match a string with the given names of an individual
854 | :type name: str
855 | :rtype: bool
856 | """
857 | (first, last) = self.get_name()
858 | return first.find(name) >= 0
859 |
860 | def birth_year_match(self, year):
861 | """Match the birth year of an individual
862 | :type year: int
863 | :rtype: bool
864 | """
865 | return self.get_birth_year() == year
866 |
867 | def birth_range_match(self, from_year, to_year):
868 | """Check if the birth year of an individual is in a given range
869 | :type from_year: int
870 | :type to_year: int
871 | :rtype: bool
872 | """
873 | birth_year = self.get_birth_year()
874 | if from_year <= birth_year <= to_year:
875 | return True
876 | return False
877 |
878 | def death_year_match(self, year):
879 | """Match the death year of an individual.
880 | :type year: int
881 | :rtype: bool
882 | """
883 | return self.get_death_year() == year
884 |
885 | def death_range_match(self, from_year, to_year):
886 | """Check if the death year of an individual is in a given range. Years are integers
887 | :type from_year: int
888 | :type to_year: int
889 | :rtype: bool
890 | """
891 | death_year = self.get_death_year()
892 | if from_year <= death_year <= to_year:
893 | return True
894 | return False
895 |
896 | def get_source_title(self):
897 | title = ""
898 | if not self.is_source():
899 | return title
900 | for child in self.get_child_elements():
901 | if child.get_tag() == GEDCOM_TAG_TITLE:
902 | title = child.get_value()
903 | return title
904 |
905 | def get_source_publication(self):
906 | publication = ""
907 | if not self.is_source():
908 | return publication
909 | for child in self.get_child_elements():
910 | if child.get_tag() == GEDCOM_TAG_PUBLICATION:
911 | publication = child.get_value()
912 | return publication
913 |
914 | def get_name(self):
915 | """Return a person's names as a tuple: (first, last, nick)
916 | :rtype: tuple
917 | """
918 | first = ""
919 | last = ""
920 | nick = ""
921 | if not self.is_individual():
922 | return first, last
923 | for child in self.get_child_elements():
924 | if child.get_tag() == GEDCOM_TAG_NAME:
925 | # some older GEDCOM files don't use child tags but instead
926 | # place the name in the value of the NAME tag
927 | if child.get_value() != "":
928 | name = child.get_value().split('/')
929 | if len(name) > 0:
930 | first = name[0].strip()
931 | if len(name) > 1:
932 | last = name[1].strip()
933 | else:
934 | for childOfChild in child.get_child_elements():
935 | if childOfChild.get_tag() == GEDCOM_TAG_GIVEN_NAME:
936 | first = childOfChild.get_value()
937 | if childOfChild.get_tag() == GEDCOM_TAG_SURNAME:
938 | last = childOfChild.get_value()
939 | if childOfChild.get_tag() == GEDCOM_TAG_NICKNAME:
940 | nick = childOfChild.get_value()
941 | for childOfChild in child.get_child_elements():
942 | if childOfChild.get_tag() == GEDCOM_TAG_NICKNAME:
943 | nick = childOfChild.get_value()
944 | return first, last, nick
945 |
946 | def get_gender(self):
947 | """Return the gender of a person in string format
948 | :rtype: str
949 | """
950 | gender = ""
951 | if not self.is_individual():
952 | return gender
953 | for child in self.get_child_elements():
954 | if child.get_tag() == GEDCOM_TAG_SEX:
955 | gender = child.get_value()
956 | return gender
957 |
958 | def is_private(self):
959 | """Return if the person is marked private in boolean format
960 | :rtype: bool
961 | """
962 | private = False
963 | if not self.is_individual():
964 | return private
965 | for child in self.get_child_elements():
966 | if child.get_tag() == GEDCOM_TAG_PRIVATE:
967 | private = child.get_value()
968 | if private == 'Y':
969 | private = True
970 | return private
971 |
972 | def get_birth_data(self):
973 | """Return the birth tuple of a person as (date, place, sources)
974 | :rtype: tuple
975 | """
976 | date = ""
977 | place = ""
978 | sources = []
979 | if not self.is_individual():
980 | return date, place, sources
981 | for child in self.get_child_elements():
982 | if child.get_tag() == GEDCOM_TAG_BIRTH:
983 | for childOfChild in child.get_child_elements():
984 | if childOfChild.get_tag() == GEDCOM_TAG_DATE:
985 | date = childOfChild.get_value()
986 | if childOfChild.get_tag() == GEDCOM_TAG_PLACE:
987 | place = childOfChild.get_value()
988 | if childOfChild.get_tag() == GEDCOM_TAG_SOURCE:
989 | sources.append(childOfChild.get_value())
990 | return date, place, sources
991 |
992 | def get_birth_year(self):
993 | """Return the birth year of a person in integer format
994 | :rtype: int
995 | """
996 | date = ""
997 | if not self.is_individual():
998 | return date
999 | for child in self.get_child_elements():
1000 | if child.get_tag() == GEDCOM_TAG_BIRTH:
1001 | for childOfChild in child.get_child_elements():
1002 | if childOfChild.get_tag() == GEDCOM_TAG_DATE:
1003 | date_split = childOfChild.get_value().split()
1004 | date = date_split[len(date_split) - 1]
1005 | if date == "":
1006 | return -1
1007 | try:
1008 | return int(date)
1009 | except:
1010 | return -1
1011 |
1012 | def get_death_data(self):
1013 | """Return the death tuple of a person as (date, place, sources)
1014 | :rtype: tuple
1015 | """
1016 | date = ""
1017 | place = ""
1018 | sources = []
1019 | if not self.is_individual():
1020 | return date, place
1021 | for child in self.get_child_elements():
1022 | if child.get_tag() == GEDCOM_TAG_DEATH:
1023 | for childOfChild in child.get_child_elements():
1024 | if childOfChild.get_tag() == GEDCOM_TAG_DATE:
1025 | date = childOfChild.get_value()
1026 | if childOfChild.get_tag() == GEDCOM_TAG_PLACE:
1027 | place = childOfChild.get_value()
1028 | if childOfChild.get_tag() == GEDCOM_TAG_SOURCE:
1029 | sources.append(childOfChild.get_value())
1030 | return date, place, sources
1031 |
1032 | def get_death_year(self):
1033 | """Return the death year of a person in integer format
1034 | :rtype: int
1035 | """
1036 | date = ""
1037 | if not self.is_individual():
1038 | return date
1039 | for child in self.get_child_elements():
1040 | if child.get_tag() == GEDCOM_TAG_DEATH:
1041 | for childOfChild in child.get_child_elements():
1042 | if childOfChild.get_tag() == GEDCOM_TAG_DATE:
1043 | date_split = childOfChild.get_value().split()
1044 | date = date_split[len(date_split) - 1]
1045 | if date == "":
1046 | return -1
1047 | try:
1048 | return int(date)
1049 | except:
1050 | return -1
1051 |
1052 | def get_burial(self):
1053 | """Return the burial tuple of a person as (date, place, sources)
1054 | :rtype: tuple
1055 | """
1056 | date = ""
1057 | place = ""
1058 | sources = []
1059 | if not self.is_individual():
1060 | return date, place
1061 | for child in self.get_child_elements():
1062 | if child.get_tag() == GEDCOM_TAG_BURIAL:
1063 | for childOfChild in child.get_child_elements():
1064 | if childOfChild.get_tag() == GEDCOM_TAG_DATE:
1065 | date = childOfChild.get_value()
1066 | if childOfChild.get_tag() == GEDCOM_TAG_PLACE:
1067 | place = childOfChild.get_value()
1068 | if childOfChild.get_tag() == GEDCOM_TAG_SOURCE:
1069 | sources.append(childOfChild.get_value())
1070 | return date, place, sources
1071 |
1072 | def get_census(self):
1073 | """Return list of census tuples (date, place, sources) for an individual
1074 | :rtype: tuple
1075 | """
1076 | census = []
1077 | if not self.is_individual():
1078 | raise ValueError("Operation only valid for elements with %s tag" % GEDCOM_TAG_INDIVIDUAL)
1079 | for child in self.get_child_elements():
1080 | if child.get_tag() == GEDCOM_TAG_CENSUS:
1081 | date = ''
1082 | place = ''
1083 | sources = []
1084 | for childOfChild in child.get_child_elements():
1085 | if childOfChild.get_tag() == GEDCOM_TAG_DATE:
1086 | date = childOfChild.get_value()
1087 | if childOfChild.get_tag() == GEDCOM_TAG_PLACE:
1088 | place = childOfChild.get_value()
1089 | if childOfChild.get_tag() == GEDCOM_TAG_SOURCE:
1090 | sources.append(childOfChild.get_value())
1091 | census.append((date, place, sources))
1092 | return census
1093 |
1094 | def get_last_change_date(self):
1095 | """Return the last updated date of a person as (date)
1096 | :rtype: str
1097 | """
1098 | date = ""
1099 | if not self.is_individual():
1100 | return date
1101 | for child in self.get_child_elements():
1102 | if child.get_tag() == GEDCOM_TAG_CHANGE:
1103 | for childOfChild in child.get_child_elements():
1104 | if childOfChild.get_tag() == GEDCOM_TAG_DATE:
1105 | date = childOfChild.get_value()
1106 | return date
1107 |
1108 | def get_occupation(self):
1109 | """Return the occupation of a person as (date)
1110 | :rtype: str
1111 | """
1112 | occupation = ""
1113 | if not self.is_individual():
1114 | return occupation
1115 | for child in self.get_child_elements():
1116 | if child.get_tag() == GEDCOM_TAG_OCCUPATION:
1117 | occupation = child.get_value()
1118 | return occupation
1119 |
1120 | def is_deceased(self):
1121 | """Check if a person is deceased
1122 | :rtype: bool
1123 | """
1124 | if not self.is_individual():
1125 | return False
1126 | for child in self.get_child_elements():
1127 | if child.get_tag() == GEDCOM_TAG_DEATH:
1128 | return True
1129 | return False
1130 |
1131 | def get_notes(self):
1132 | s = ''
1133 | if not self.is_individual():
1134 | return s
1135 | for child in self.get_child_elements():
1136 | if child.get_tag() == GEDCOM_TAG_NOTES:
1137 | s = child.get_value()
1138 | return s
1139 |
1140 | def get_individual(self):
1141 | """Return this element and all of its sub-elements
1142 | :rtype: str
1143 | """
1144 | result = self.__unicode__()
1145 | for child_element in self.get_child_elements():
1146 | result += child_element.get_individual()
1147 | return result
1148 |
1149 | def __str__(self):
1150 | """:rtype: str"""
1151 | if version_info[0] >= 3:
1152 | return self.__unicode__()
1153 | else:
1154 | return self.__unicode__().encode('utf-8')
1155 |
1156 | def __unicode__(self):
1157 | """Format this element as its original string
1158 | :rtype: str
1159 | """
1160 | if self.get_level() < 0:
1161 | return ''
1162 | result = str(self.get_level())
1163 | if self.get_pointer() != "":
1164 | result += ' ' + self.get_pointer()
1165 | result += ' ' + self.get_tag()
1166 | if self.get_value() != "":
1167 | result += ' ' + self.get_value()
1168 | result += self.__crlf
1169 | return result
1170 |
1171 |
1172 | class Source:
1173 | def __init__(self):
1174 | self.title = ""
1175 | self.publication = ""
1176 |
1177 | class Family:
1178 | def __init__(self):
1179 | self.child_id = []
1180 | self.spouse_id = ""
1181 |
1182 | class Person:
1183 | def __init__(self):
1184 | self.id = ""
1185 | self.first_name = ""
1186 | self.surname = ""
1187 | self.nick_name = ""
1188 | self.notes = ""
1189 | self.text = ""
1190 | self.occupation = ""
1191 | self.gender = ""
1192 | self.birth_date = False
1193 | self.birth_place = ""
1194 | self.death_date = False
1195 | self.death_place = ""
1196 | self.parent_id = []
1197 | self.family = []
1198 |
1199 | class GedcomParser:
1200 | def __init__(self, filepath):
1201 | self.__g = Gedcom(filepath)
1202 |
1203 | def get_sources(self):
1204 | sources = {}
1205 | for e in self.__g.get_element_list():
1206 | if e.is_source():
1207 | s = Source()
1208 | s.id = self.__element_get_id(e)
1209 | s.title = e.get_source_title()
1210 | s.publication = e.get_source_publication()
1211 | sources[s.id] = s
1212 | return(sources)
1213 |
1214 | def get_persons(self):
1215 | persons = {}
1216 | for e in self.__g.get_element_list():
1217 | if e.is_individual():
1218 | person = Person()
1219 | person.id = self.__element_get_id(e)
1220 | person.first_name = e.get_name()[0]
1221 | person.surname = e.get_name()[1]
1222 | person.nick_name = e.get_name()[2]
1223 | person.notes = e.get_notes()
1224 | person.occupation = e.get_occupation()
1225 | person.gender = e.get_gender()
1226 | try:
1227 | person.birth_date = datetime.strptime(e.get_birth_data()[0], '%d %b %Y').date()
1228 | except:
1229 | pass
1230 | try:
1231 | person.death_date = datetime.strptime(e.get_death_data()[0], '%d %b %Y').date()
1232 | except:
1233 | pass
1234 | # parents
1235 | for parent_element in self.__g.get_parents(e):
1236 | person.parent_id.append(self.__element_get_id(parent_element))
1237 | # families
1238 | for family_element in self.__g.get_families(e):
1239 | family = Family()
1240 | for tag in ['HUSB','WIFE']:
1241 | p = self.__g.get_family_members(family_element, tag)
1242 | if len(p) > 0:
1243 | spouse_id = self.__element_get_id(p[0])
1244 | if (person.id != spouse_id):
1245 | family.spouse_id = spouse_id
1246 | pl = self.__g.get_family_members(family_element, 'CHIL')
1247 | if len(pl) > 0:
1248 | for p in pl:
1249 | family.child_id.append(self.__element_get_id(p))
1250 | if len(family.spouse_id) > 0 or len(family.child_id) > 0:
1251 | person.family.append(family)
1252 | # add dict to list
1253 | persons[person.id] = person
1254 | return(persons)
1255 |
1256 | def __element_get_id(self, e):
1257 | return(e.get_pointer().replace("@",""))
--------------------------------------------------------------------------------