Compare commits

..

No commits in common. "5860b3d25c46c005d00a79515d3f214ab72875a4" and "2874fe35d13358658d35a675b6bad198e4f7ff93" have entirely different histories.

5 changed files with 547 additions and 1501 deletions

View File

@ -22,7 +22,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext _ = glocale.translation.gettext
MODULE_VERSION = "6.0" MODULE_VERSION = "5.1"
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
# #
@ -34,14 +34,14 @@ register(
VIEW, VIEW,
id="mytimelineview", id="mytimelineview",
name=_("MyTimeline"), name=_("MyTimeline"),
description=_("A vertical timeline view showing all events in the database"), description=_("A vertical timeline view showing family events including birth, death, and marriage"),
version="1.0", version="1.0",
gramps_target_version=MODULE_VERSION, gramps_target_version=MODULE_VERSION,
status=STABLE, status=STABLE,
fname="MyTimeline.py", fname="MyTimeline.py",
authors=["Daniel Viegas"], authors=["Daniel Viegas"],
authors_email=["dlviegas@gmail.com"], authors_email=["dlviegas@gmail.com"],
category=("Events", _("Events")), category=("Families", _("Families")),
viewclass="MyTimelineView", viewclass="MyTimelineView",
) )

File diff suppressed because it is too large Load Diff

View File

@ -4,25 +4,14 @@ Generate a huge demo family for Gramps testing
""" """
import random import random
import xml.etree.ElementTree as ET from datetime import datetime, timedelta
from dataclasses import dataclass
from datetime import datetime
from typing import Optional, List, Tuple, Dict
# Set seed for deterministic event generation # Set seed for deterministic event generation
random.seed(42) random.seed(42)
# Constants # Generate unique handles
EVENT_ID_OFFSET = 10 def gen_handle(prefix, num):
FAMILY_ID_OFFSET = 100 return f"_{prefix}{num:08d}"
EVENT_ID_START_OFFSET = 2
MIN_MONTH = 1
MAX_MONTH = 12
MIN_DAY = 1
MAX_DAY = 28
GRAMPS_XML_VERSION = "5.1.0"
GRAMPS_XML_NAMESPACE = "http://gramps-project.org/xml/1.7.1/"
GRAMPS_XML_DTD = "http://gramps-project.org/xml/1.7.1/grampsxml.dtd"
# Event types to add # Event types to add
EVENT_TYPES = [ EVENT_TYPES = [
@ -40,115 +29,10 @@ EVENT_TYPES = [
("Cremation", 0.2, None, None), # 20% chance if death exists, at death time ("Cremation", 0.2, None, None), # 20% chance if death exists, at death time
] ]
# Name lists # Generate additional events for a person
MALE_NAMES = [ def gen_additional_events(pid, first_name, surname, birth_year, death_year=None):
"James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", events = []
"Thomas", "Charles", "Daniel", "Matthew", "Anthony", "Mark", "Donald", "Steven", event_id_offset = pid * 10 + 2 # Start after birth and death events
"Paul", "Andrew", "Joshua", "Kenneth", "Kevin", "Brian", "George", "Timothy",
"Ronald", "Jason", "Edward", "Jeffrey", "Ryan", "Jacob", "Gary", "Nicholas",
"Eric", "Jonathan", "Stephen", "Larry", "Justin", "Scott", "Brandon", "Benjamin"
]
FEMALE_NAMES = [
"Mary", "Patricia", "Jennifer", "Linda", "Elizabeth", "Barbara", "Susan",
"Jessica", "Sarah", "Karen", "Nancy", "Lisa", "Betty", "Margaret", "Sandra",
"Ashley", "Kimberly", "Emily", "Donna", "Michelle", "Dorothy", "Carol",
"Amanda", "Melissa", "Deborah", "Stephanie", "Rebecca", "Sharon", "Laura",
"Cynthia", "Kathleen", "Amy", "Angela", "Shirley", "Anna", "Brenda", "Pamela",
"Emma", "Nicole", "Helen", "Samantha", "Katherine", "Christine", "Debra"
]
SURNAMES = [
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
"Rodriguez", "Martinez", "Hernandez", "Lopez", "Wilson", "Anderson", "Thomas",
"Taylor", "Moore", "Jackson", "Martin", "Lee", "Thompson", "White", "Harris",
"Sanchez", "Clark", "Ramirez", "Lewis", "Robinson", "Walker", "Young", "Allen",
"King", "Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores", "Green", "Adams"
]
OCCUPATIONS = [
"Farmer", "Teacher", "Engineer", "Doctor", "Lawyer", "Merchant",
"Carpenter", "Blacksmith", "Sailor", "Soldier", "Clerk", "Nurse"
]
PLACES = [
"New York", "London", "Paris", "Berlin", "Rome", "Madrid", "Amsterdam",
"Vienna", "Prague", "Warsaw", "Stockholm", "Copenhagen"
]
@dataclass
class EventData:
"""Data structure for an event."""
handle: str
event_type: str
year: int
month: int
day: int
description: str
event_id: int
@dataclass
class PersonData:
"""Data structure for person information."""
handle: str
name: str
surname: str
birth: int
death: Optional[int]
gender: str
parentin: List[str]
childof: List[str]
@dataclass
class FamilyData:
"""Data structure for family information."""
handle: str
father_handle: str
mother_handle: str
children_handles: List[str]
marriage_year: int
marriage_handle: str
family_id: int
def gen_handle(prefix: str, num: int) -> str:
"""Generate unique handle."""
return f"_{prefix}{num:08d}"
def create_event_element(event_data: EventData) -> ET.Element:
"""Create an XML element for an event."""
event_elem = ET.Element("event")
event_elem.set("handle", event_data.handle)
event_elem.set("change", str(int(datetime.now().timestamp())))
event_elem.set("id", f"E{event_data.event_id:04d}")
type_elem = ET.SubElement(event_elem, "type")
type_elem.text = event_data.event_type
date_elem = ET.SubElement(event_elem, "dateval")
date_elem.set("val", f"{event_data.year}-{event_data.month:02d}-{event_data.day:02d}")
if event_data.description:
desc_elem = ET.SubElement(event_elem, "description")
desc_elem.text = event_data.description
return event_elem
def gen_additional_events(
pid: int,
first_name: str,
surname: str,
birth_year: int,
death_year: Optional[int] = None
) -> List[Tuple[str, EventData]]:
"""Generate additional events for a person."""
events: List[Tuple[str, EventData]] = []
event_id_offset = pid * EVENT_ID_OFFSET + EVENT_ID_START_OFFSET
for event_type, probability, min_years, max_years in EVENT_TYPES: for event_type, probability, min_years, max_years in EVENT_TYPES:
if random.random() > probability: if random.random() > probability:
@ -159,18 +43,19 @@ def gen_additional_events(
if not death_year: if not death_year:
continue continue
event_year = death_year event_year = death_year
event_month = random.randint(MIN_MONTH, MAX_MONTH) event_month = random.randint(1, 12)
event_day = random.randint(MIN_DAY, MAX_DAY) event_day = random.randint(1, 28)
else: else:
if max_years is None: if max_years is None:
continue continue
event_year = birth_year + random.randint(min_years, max_years) event_year = birth_year + random.randint(min_years, max_years)
if death_year and event_year > death_year: if death_year and event_year > death_year:
continue continue
event_month = random.randint(MIN_MONTH, MAX_MONTH) event_month = random.randint(1, 12)
event_day = random.randint(MIN_DAY, MAX_DAY) event_day = random.randint(1, 28)
event_handle = gen_handle("EVENT", event_id_offset) event_handle = gen_handle("EVENT", event_id_offset)
event_id_offset += 1
# Generate description based on event type # Generate description based on event type
if event_type == "Education": if event_type == "Education":
@ -178,12 +63,16 @@ def gen_additional_events(
elif event_type == "Graduation": elif event_type == "Graduation":
description = f"Graduation - {first_name} {surname}" description = f"Graduation - {first_name} {surname}"
elif event_type == "Occupation": elif event_type == "Occupation":
occupation = random.choice(OCCUPATIONS) occupations = ["Farmer", "Teacher", "Engineer", "Doctor", "Lawyer", "Merchant",
"Carpenter", "Blacksmith", "Sailor", "Soldier", "Clerk", "Nurse"]
occupation = random.choice(occupations)
description = f"{occupation} - {first_name} {surname}" description = f"{occupation} - {first_name} {surname}"
elif event_type == "Military Service": elif event_type == "Military Service":
description = f"Military Service - {first_name} {surname}" description = f"Military Service - {first_name} {surname}"
elif event_type == "Residence": elif event_type == "Residence":
place = random.choice(PLACES) places = ["New York", "London", "Paris", "Berlin", "Rome", "Madrid", "Amsterdam",
"Vienna", "Prague", "Warsaw", "Stockholm", "Copenhagen"]
place = random.choice(places)
description = f"Residence in {place} - {first_name} {surname}" description = f"Residence in {place} - {first_name} {surname}"
elif event_type == "Emigration": elif event_type == "Emigration":
description = f"Emigration - {first_name} {surname}" description = f"Emigration - {first_name} {surname}"
@ -194,218 +83,136 @@ def gen_additional_events(
else: else:
description = f"{event_type} of {surname}, {first_name}" description = f"{event_type} of {surname}, {first_name}"
event_data = EventData( event_xml = f""" <event handle="{event_handle}" change="{int(datetime.now().timestamp())}" id="E{event_id_offset-1:04d}">
handle=event_handle, <type>{event_type}</type>
event_type=event_type, <dateval val="{event_year}-{event_month:02d}-{event_day:02d}"/>
year=event_year, <description>{description}</description>
month=event_month, </event>
day=event_day, """
description=description, events.append((event_handle, event_xml))
event_id=event_id_offset
)
events.append((event_handle, event_data))
event_id_offset += 1
return events return events
# Generate a person
def gen_person( def gen_person(pid, first_name, surname, birth_year, death_year=None, gender="M",
pid: int, parentin_families=None, childof_families=None, reuse_additional_events=None):
first_name: str,
surname: str,
birth_year: int,
death_year: Optional[int] = None,
gender: str = "M",
parentin_families: Optional[List[str]] = None,
childof_families: Optional[List[str]] = None,
reuse_additional_events: Optional[List[Tuple[str, EventData]]] = None
) -> Tuple[ET.Element, ET.Element, Optional[ET.Element], List[ET.Element], List[Tuple[str, EventData]]]:
"""Generate a person with all associated events."""
handle = gen_handle("PERSON", pid) handle = gen_handle("PERSON", pid)
birth_handle = gen_handle("EVENT", pid * EVENT_ID_OFFSET) birth_handle = gen_handle("EVENT", pid * 10)
death_handle = gen_handle("EVENT", pid * EVENT_ID_OFFSET + 1) if death_year else None death_handle = gen_handle("EVENT", pid * 10 + 1) if death_year else None
# Create person element person_xml = f""" <person handle="{handle}" change="{int(datetime.now().timestamp())}" id="I{pid:04d}">
person_elem = ET.Element("person") <gender>{gender}</gender>
person_elem.set("handle", handle) <name type="Birth Name">
person_elem.set("change", str(int(datetime.now().timestamp()))) <first>{first_name}</first>
person_elem.set("id", f"I{pid:04d}") <surname>{surname}</surname>
</name>
gender_elem = ET.SubElement(person_elem, "gender") <eventref hlink="{birth_handle}" role="Primary"/>
gender_elem.text = gender """
name_elem = ET.SubElement(person_elem, "name")
name_elem.set("type", "Birth Name")
first_elem = ET.SubElement(name_elem, "first")
first_elem.text = first_name
surname_elem = ET.SubElement(name_elem, "surname")
surname_elem.text = surname
# Birth event reference
birth_ref = ET.SubElement(person_elem, "eventref")
birth_ref.set("hlink", birth_handle)
birth_ref.set("role", "Primary")
# Death event reference
if death_handle: if death_handle:
death_ref = ET.SubElement(person_elem, "eventref") person_xml += f""" <eventref hlink="{death_handle}" role="Primary"/>
death_ref.set("hlink", death_handle) """
death_ref.set("role", "Primary")
# Add additional events - reuse if provided, otherwise generate new # Add additional events - reuse if provided, otherwise generate new
if reuse_additional_events is not None: if reuse_additional_events is not None:
# reuse_additional_events is a list of (handle, xml) tuples
additional_events = reuse_additional_events additional_events = reuse_additional_events
else: else:
additional_events = gen_additional_events(pid, first_name, surname, birth_year, death_year) additional_events = gen_additional_events(pid, first_name, surname, birth_year, death_year)
for event_handle, _ in additional_events: for event_handle, _ in additional_events:
event_ref = ET.SubElement(person_elem, "eventref") person_xml += f""" <eventref hlink="{event_handle}" role="Primary"/>
event_ref.set("hlink", event_handle) """
event_ref.set("role", "Primary")
# Add parentin references # Add parentin references (for fathers and mothers)
if parentin_families: if parentin_families:
for family_handle in parentin_families: for family_handle in parentin_families:
parentin_elem = ET.SubElement(person_elem, "parentin") person_xml += f""" <parentin hlink="{family_handle}"/>
parentin_elem.set("hlink", family_handle) """
# Add childof references (for children)
# Add childof references
if childof_families: if childof_families:
for family_handle in childof_families: for family_handle in childof_families:
childof_elem = ET.SubElement(person_elem, "childof") person_xml += f""" <childof hlink="{family_handle}"/>
childof_elem.set("hlink", family_handle) """
person_xml += """ </person>
"""
# Birth event # Birth event
birth_month = random.randint(MIN_MONTH, MAX_MONTH) birth_month = random.randint(1, 12)
birth_day = random.randint(MIN_DAY, MAX_DAY) birth_day = random.randint(1, 28)
birth_event_data = EventData( birth_event = f""" <event handle="{birth_handle}" change="{int(datetime.now().timestamp())}" id="E{pid*10:04d}">
handle=birth_handle, <type>Birth</type>
event_type="Birth", <dateval val="{birth_year}-{birth_month:02d}-{birth_day:02d}"/>
year=birth_year, <description>Birth of {surname}, {first_name}</description>
month=birth_month, </event>
day=birth_day, """
description=f"Birth of {surname}, {first_name}",
event_id=pid * EVENT_ID_OFFSET
)
birth_event = create_event_element(birth_event_data)
# Death event # Death event
death_event: Optional[ET.Element] = None death_event = ""
if death_handle and death_year: if death_handle and death_year:
death_month = random.randint(MIN_MONTH, MAX_MONTH) death_month = random.randint(1, 12)
death_day = random.randint(MIN_DAY, MAX_DAY) death_day = random.randint(1, 28)
death_event_data = EventData( death_event = f""" <event handle="{death_handle}" change="{int(datetime.now().timestamp())}" id="E{pid*10+1:04d}">
handle=death_handle, <type>Death</type>
event_type="Death", <dateval val="{death_year}-{death_month:02d}-{death_day:02d}"/>
year=death_year, <description>Death of {surname}, {first_name}</description>
month=death_month, </event>
day=death_day, """
description=f"Death of {surname}, {first_name}",
event_id=pid * EVENT_ID_OFFSET + 1
)
death_event = create_event_element(death_event_data)
# Convert additional events to XML elements # Collect all additional events (return tuples for reuse, XML strings for output)
all_additional_events_xml = [create_event_element(event_data) for _, event_data in additional_events] all_additional_events_xml = [event_xml for _, event_xml in additional_events]
return person_elem, birth_event, death_event, all_additional_events_xml, additional_events return person_xml, birth_event, death_event, all_additional_events_xml, additional_events
# Generate a family
def gen_family( def gen_family(fid, father_handle, mother_handle, marriage_year, children_handles):
fid: int,
father_handle: str,
mother_handle: str,
marriage_year: int,
children_handles: List[str]
) -> Tuple[ET.Element, ET.Element]:
"""Generate a family with marriage event."""
handle = gen_handle("FAMILY", fid) handle = gen_handle("FAMILY", fid)
marriage_handle = gen_handle("EVENT", fid * FAMILY_ID_OFFSET) marriage_handle = gen_handle("EVENT", fid * 100)
# Create family element
family_elem = ET.Element("family")
family_elem.set("handle", handle)
family_elem.set("change", str(int(datetime.now().timestamp())))
family_elem.set("id", f"F{fid:04d}")
rel_elem = ET.SubElement(family_elem, "rel")
rel_elem.set("type", "Married")
father_elem = ET.SubElement(family_elem, "father")
father_elem.set("hlink", father_handle)
mother_elem = ET.SubElement(family_elem, "mother")
mother_elem.set("hlink", mother_handle)
family_xml = f""" <family handle="{handle}" change="{int(datetime.now().timestamp())}" id="F{fid:04d}">
<rel type="Married"/>
<father hlink="{father_handle}"/>
<mother hlink="{mother_handle}"/>
"""
for child_handle in children_handles: for child_handle in children_handles:
child_elem = ET.SubElement(family_elem, "childref") family_xml += f""" <childref hlink="{child_handle}"/>
child_elem.set("hlink", child_handle) """
family_xml += f""" <eventref hlink="{marriage_handle}" role="Family"/>
marriage_ref = ET.SubElement(family_elem, "eventref") </family>
marriage_ref.set("hlink", marriage_handle) """
marriage_ref.set("role", "Family")
# Marriage event # Marriage event
marriage_month = random.randint(MIN_MONTH, MAX_MONTH) marriage_month = random.randint(1, 12)
marriage_day = random.randint(MIN_DAY, MAX_DAY) marriage_day = random.randint(1, 28)
marriage_event_data = EventData( marriage_event = f""" <event handle="{marriage_handle}" change="{int(datetime.now().timestamp())}" id="E{fid*100:04d}">
handle=marriage_handle, <type>Marriage</type>
event_type="Marriage", <dateval val="{marriage_year}-{marriage_month:02d}-{marriage_day:02d}"/>
year=marriage_year, <description>Marriage</description>
month=marriage_month, </event>
day=marriage_day, """
description="Marriage",
event_id=fid * FAMILY_ID_OFFSET
)
marriage_event = create_event_element(marriage_event_data)
return family_elem, marriage_event return family_xml, marriage_event
# First names
male_names = ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph",
"Thomas", "Charles", "Daniel", "Matthew", "Anthony", "Mark", "Donald", "Steven",
"Paul", "Andrew", "Joshua", "Kenneth", "Kevin", "Brian", "George", "Timothy",
"Ronald", "Jason", "Edward", "Jeffrey", "Ryan", "Jacob", "Gary", "Nicholas",
"Eric", "Jonathan", "Stephen", "Larry", "Justin", "Scott", "Brandon", "Benjamin"]
def create_gramps_xml_document( female_names = ["Mary", "Patricia", "Jennifer", "Linda", "Elizabeth", "Barbara", "Susan",
events: List[ET.Element], "Jessica", "Sarah", "Karen", "Nancy", "Lisa", "Betty", "Margaret", "Sandra",
people: List[ET.Element], "Ashley", "Kimberly", "Emily", "Donna", "Michelle", "Dorothy", "Carol",
families: List[ET.Element] "Amanda", "Melissa", "Deborah", "Stephanie", "Rebecca", "Sharon", "Laura",
) -> ET.ElementTree: "Cynthia", "Kathleen", "Amy", "Angela", "Shirley", "Anna", "Brenda", "Pamela",
"""Create the complete Gramps XML document.""" "Emma", "Nicole", "Helen", "Samantha", "Katherine", "Christine", "Debra"]
# Create root element
database = ET.Element("database")
database.set("xmlns", GRAMPS_XML_NAMESPACE)
# Header surnames = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
header = ET.SubElement(database, "header") "Rodriguez", "Martinez", "Hernandez", "Lopez", "Wilson", "Anderson", "Thomas",
created = ET.SubElement(header, "created") "Taylor", "Moore", "Jackson", "Martin", "Lee", "Thompson", "White", "Harris",
created.set("date", datetime.now().strftime('%Y-%m-%d')) "Sanchez", "Clark", "Ramirez", "Lewis", "Robinson", "Walker", "Young", "Allen",
created.set("version", GRAMPS_XML_VERSION) "King", "Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores", "Green", "Adams"]
researcher = ET.SubElement(header, "researcher") def main():
resname = ET.SubElement(researcher, "resname")
resname.text = "Demo Family Generator"
# Tags (empty)
ET.SubElement(database, "tags")
# Events
events_elem = ET.SubElement(database, "events")
for event in events:
events_elem.append(event)
# People
people_elem = ET.SubElement(database, "people")
for person in people:
people_elem.append(person)
# Families
families_elem = ET.SubElement(database, "families")
for family in families:
families_elem.append(family)
return ET.ElementTree(database)
def main() -> None:
"""Main function to generate the demo family."""
print("Generating huge demo family...") print("Generating huge demo family...")
# Generate main family # Generate main family
@ -427,21 +234,17 @@ def main() -> None:
) )
all_additional_events = father_additional_xml + mother_additional_xml all_additional_events = father_additional_xml + mother_additional_xml
all_events = [father_birth, mother_birth]
if father_death:
all_events.append(father_death)
if mother_death:
all_events.append(mother_death)
# Generate 15 children # Generate 15 children
children: List[ET.Element] = [] children = []
child_handles: List[str] = [] child_handles = []
child_additional_events_map: Dict[int, List[Tuple[str, EventData]]] = {} child_events = []
child_additional_events_map = {} # Store additional events by child_id
child_id = 3 child_id = 3
for i in range(15): for i in range(15):
gender = "M" if i % 2 == 0 else "F" gender = "M" if i % 2 == 0 else "F"
first_name = random.choice(MALE_NAMES if gender == "M" else FEMALE_NAMES) first_name = random.choice(male_names if gender == "M" else female_names)
birth_year = 1970 + (i * 2) # Spread births from 1970 to 1998 birth_year = 1970 + (i * 2) # Spread births from 1970 to 1998
death_year = birth_year + random.randint(60, 90) if random.random() < 0.3 else None # 30% chance of death death_year = birth_year + random.randint(60, 90) if random.random() < 0.3 else None # 30% chance of death
@ -453,9 +256,9 @@ def main() -> None:
children.append(child_person) children.append(child_person)
child_handles.append(child_handle) child_handles.append(child_handle)
all_events.append(child_birth) child_events.append(child_birth)
if child_death: if child_death:
all_events.append(child_death) child_events.append(child_death)
# Store tuples for reuse when regenerating # Store tuples for reuse when regenerating
child_additional_events_map[child_id] = child_additional_tuples child_additional_events_map[child_id] = child_additional_tuples
all_additional_events.extend(child_additional_xml) all_additional_events.extend(child_additional_xml)
@ -463,66 +266,40 @@ def main() -> None:
# Generate family # Generate family
family_id = 1 family_id = 1
family_elem, marriage_event = gen_family(family_id, father_handle, mother_handle, 1969, child_handles) family_xml, marriage_event = gen_family(family_id, father_handle, mother_handle, 1969, child_handles)
all_events.append(marriage_event)
families: List[ET.Element] = [family_elem]
# Track person data for regeneration (needed for children who become parents) # Track person data for regeneration (needed for children who become parents)
person_data: Dict[int, PersonData] = {} import re
person_data = {}
# Store initial person data # Store initial person data
person_data[father_id] = PersonData( person_data[father_id] = {"handle": father_handle, "name": "John", "surname": "Smith",
handle=father_handle, "birth": 1950, "death": 2010, "gender": "M",
name="John", "parentin": [main_family_handle], "childof": []}
surname="Smith", person_data[mother_id] = {"handle": mother_handle, "name": "Mary", "surname": "Smith",
birth=1950, "birth": 1952, "death": 2015, "gender": "F",
death=2010, "parentin": [main_family_handle], "childof": []}
gender="M",
parentin=[main_family_handle],
childof=[]
)
person_data[mother_id] = PersonData(
handle=mother_handle,
name="Mary",
surname="Smith",
birth=1952,
death=2015,
gender="F",
parentin=[main_family_handle],
childof=[]
)
for i, child_handle in enumerate(child_handles): for i, child_handle in enumerate(child_handles):
child_pid = 3 + i child_pid = 3 + i
gender = "M" if i % 2 == 0 else "F" gender = "M" if i % 2 == 0 else "F"
# Extract name from generated child XML # Extract name from generated child XML
name_elem = children[i].find(".//first") child_xml = children[i]
first_name = name_elem.text if name_elem is not None and name_elem.text else random.choice(MALE_NAMES if gender == "M" else FEMALE_NAMES) name_match = re.search(r'<first>([^<]+)</first>', child_xml)
first_name = name_match.group(1) if name_match else random.choice(male_names if gender == "M" else female_names)
birth_year = 1970 + (i * 2) birth_year = 1970 + (i * 2)
# Extract death year from events if it exists # Extract death year from child_events if it exists
death_year = None death_year = None
for event in all_events: for event in child_events:
if event.get("id") == f"E{child_pid * EVENT_ID_OFFSET + 1:04d}": if f"id=\"E{child_pid*10+1:04d}\"" in event:
date_elem = event.find(".//dateval") match = re.search(r'val="(\d{4})', event)
if date_elem is not None: if match:
date_val = date_elem.get("val", "") death_year = int(match.group(1))
if date_val: person_data[child_pid] = {"handle": child_handle, "name": first_name, "surname": "Smith",
try: "birth": birth_year, "death": death_year, "gender": gender,
death_year = int(date_val.split("-")[0]) "parentin": [], "childof": [main_family_handle]}
except (ValueError, IndexError):
pass
person_data[child_pid] = PersonData(
handle=child_handle,
name=first_name,
surname="Smith",
birth=birth_year,
death=death_year,
gender=gender,
parentin=[],
childof=[main_family_handle]
)
# Generate grandchildren (children of first 5 children) # Generate grandchildren (children of first 5 children)
grandchildren: List[ET.Element] = [] grandchildren = []
grandchild_events = []
grandchild_id = child_id grandchild_id = child_id
for i in range(5): # First 5 children have children for i in range(5): # First 5 children have children
@ -532,55 +309,41 @@ def main() -> None:
spouse_gender = "F" if parent_gender == "M" else "M" spouse_gender = "F" if parent_gender == "M" else "M"
# Create spouse # Create spouse
spouse_name = random.choice(FEMALE_NAMES if spouse_gender == "F" else MALE_NAMES) spouse_name = random.choice(female_names if spouse_gender == "F" else male_names)
spouse_birth = 1970 + (i * 2) + random.randint(-2, 2) spouse_birth = 1970 + (i * 2) + random.randint(-2, 2)
spouse_handle = gen_handle("PERSON", grandchild_id) spouse_handle = gen_handle("PERSON", grandchild_id)
child_family_handle = gen_handle("FAMILY", family_id + 1) child_family_handle = gen_handle("FAMILY", family_id + 1)
person_data[grandchild_id] = PersonData( person_data[grandchild_id] = {"handle": spouse_handle, "name": spouse_name, "surname": "Smith",
handle=spouse_handle, "birth": spouse_birth, "death": None, "gender": spouse_gender,
name=spouse_name, "parentin": [child_family_handle], "childof": []}
surname="Smith",
birth=spouse_birth,
death=None,
gender=spouse_gender,
parentin=[child_family_handle],
childof=[]
)
spouse_person, spouse_birth_event, spouse_death_event, spouse_additional_xml, _ = gen_person( spouse_person, spouse_birth_event, spouse_death_event, spouse_additional_xml, _ = gen_person(
grandchild_id, spouse_name, "Smith", spouse_birth, None, spouse_gender, grandchild_id, spouse_name, "Smith", spouse_birth, None, spouse_gender,
parentin_families=[child_family_handle] parentin_families=[child_family_handle]
) )
grandchildren.append(spouse_person) grandchildren.append(spouse_person)
all_events.append(spouse_birth_event) grandchild_events.append(spouse_birth_event)
if spouse_death_event: if spouse_death_event:
all_events.append(spouse_death_event) grandchild_events.append(spouse_death_event)
all_additional_events.extend(spouse_additional_xml) all_additional_events.extend(spouse_additional_xml)
grandchild_id += 1 grandchild_id += 1
# Update parent to include parentin reference # Update parent to include parentin reference
person_data[parent_pid].parentin.append(child_family_handle) person_data[parent_pid]["parentin"].append(child_family_handle)
# Create 3-5 children per couple # Create 3-5 children per couple
num_grandchildren = random.randint(3, 5) num_grandchildren = random.randint(3, 5)
grandchild_handles: List[str] = [] grandchild_handles = []
for j in range(num_grandchildren): for j in range(num_grandchildren):
gchild_gender = "M" if j % 2 == 0 else "F" gchild_gender = "M" if j % 2 == 0 else "F"
gchild_name = random.choice(MALE_NAMES if gchild_gender == "M" else FEMALE_NAMES) gchild_name = random.choice(male_names if gchild_gender == "M" else female_names)
gchild_birth = 1995 + (i * 3) + j gchild_birth = 1995 + (i * 3) + j
gchild_handle = gen_handle("PERSON", grandchild_id) gchild_handle = gen_handle("PERSON", grandchild_id)
person_data[grandchild_id] = PersonData( person_data[grandchild_id] = {"handle": gchild_handle, "name": gchild_name, "surname": "Smith",
handle=gchild_handle, "birth": gchild_birth, "death": None, "gender": gchild_gender,
name=gchild_name, "parentin": [], "childof": [child_family_handle]}
surname="Smith",
birth=gchild_birth,
death=None,
gender=gchild_gender,
parentin=[],
childof=[child_family_handle]
)
gchild_person, gchild_birth_event, gchild_death_event, gchild_additional_xml, _ = gen_person( gchild_person, gchild_birth_event, gchild_death_event, gchild_additional_xml, _ = gen_person(
grandchild_id, gchild_name, "Smith", gchild_birth, None, gchild_gender, grandchild_id, gchild_name, "Smith", gchild_birth, None, gchild_gender,
@ -588,17 +351,17 @@ def main() -> None:
) )
grandchildren.append(gchild_person) grandchildren.append(gchild_person)
grandchild_handles.append(gchild_handle) grandchild_handles.append(gchild_handle)
all_events.append(gchild_birth_event) grandchild_events.append(gchild_birth_event)
if gchild_death_event: if gchild_death_event:
all_events.append(gchild_death_event) grandchild_events.append(gchild_death_event)
all_additional_events.extend(gchild_additional_xml) all_additional_events.extend(gchild_additional_xml)
grandchild_id += 1 grandchild_id += 1
# Create family for this couple # Create family for this couple
family_id += 1 family_id += 1
fam_elem, fam_marriage = gen_family(family_id, parent_handle, spouse_handle, 1990 + i, grandchild_handles) fam_xml, fam_marriage = gen_family(family_id, parent_handle, spouse_handle, 1990 + i, grandchild_handles)
families.append(fam_elem) family_xml += fam_xml
all_events.append(fam_marriage) child_events.append(fam_marriage)
# Regenerate children XMLs with updated family references # Regenerate children XMLs with updated family references
# We need to regenerate to update family references, but reuse the same events # We need to regenerate to update family references, but reuse the same events
@ -609,41 +372,63 @@ def main() -> None:
# Reuse the original additional events to ensure consistency # Reuse the original additional events to ensure consistency
original_additional_events = child_additional_events_map.get(child_pid, []) original_additional_events = child_additional_events_map.get(child_pid, [])
child_person, _, _, _, _ = gen_person( child_person, _, _, _, _ = gen_person(
child_pid, data.name, data.surname, data.birth, data.death, data.gender, child_pid, data["name"], data["surname"], data["birth"], data["death"], data["gender"],
parentin_families=data.parentin, childof_families=data.childof, parentin_families=data["parentin"], childof_families=data["childof"],
reuse_additional_events=original_additional_events reuse_additional_events=original_additional_events
) )
children.append(child_person) children.append(child_person)
# Add all additional events to events list # Write XML file
all_events.extend(all_additional_events) xml_content = f"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE database PUBLIC "-//Gramps//DTD Gramps XML 1.7.1//EN"
"http://gramps-project.org/xml/1.7.1/grampsxml.dtd">
<database xmlns="http://gramps-project.org/xml/1.7.1/">
<header>
<created date="{datetime.now().strftime('%Y-%m-%d')}" version="5.1.0"/>
<researcher>
<resname>Demo Family Generator</resname>
</researcher>
</header>
<tags>
</tags>
<events>
{father_birth}
{father_death}
{mother_birth}
{mother_death}
{marriage_event}
"""
# Create complete XML document for event in child_events:
people = [father_person, mother_person] + children + grandchildren xml_content += event
tree = create_gramps_xml_document(all_events, people, families) for event in grandchild_events:
xml_content += event
for event in all_additional_events:
xml_content += event
# Write XML file with proper formatting xml_content += """ </events>
# ET.indent is only available in Python 3.9+, so we'll format manually if needed <people>
try: """
ET.indent(tree, space=" ") xml_content += father_person
except AttributeError: xml_content += mother_person
# Python < 3.9 doesn't have indent, will write without indentation for child in children:
pass xml_content += child
tree.write("demo_family.gramps", encoding="utf-8", xml_declaration=True) for grandchild in grandchildren:
xml_content += grandchild
# Add DOCTYPE declaration (ElementTree doesn't support this directly) xml_content += """ </people>
with open("demo_family.gramps", "r", encoding="utf-8") as f: <families>
content = f.read() """
xml_content += family_xml
# Insert DOCTYPE after XML declaration xml_content += """ </families>
doctype = f'<!DOCTYPE database PUBLIC "-//Gramps//DTD Gramps XML 1.7.1//EN"\n"{GRAMPS_XML_DTD}">\n' </database>
content = content.replace('<?xml version="1.0" encoding="utf-8"?>', """
f'<?xml version="1.0" encoding="UTF-8"?>\n{doctype}', 1)
with open("demo_family.gramps", "w", encoding="utf-8") as f: with open("demo_family.gramps", "w", encoding="utf-8") as f:
f.write(content) f.write(xml_content)
total_events = len(all_events) total_events = len(child_events) + len(grandchild_events) + len(all_additional_events)
print(f"Generated demo_family.gramps with:") print(f"Generated demo_family.gramps with:")
print(f" - 2 parents (John and Mary Smith)") print(f" - 2 parents (John and Mary Smith)")
print(f" - 15 children") print(f" - 15 children")
@ -654,6 +439,6 @@ def main() -> None:
print(f" - {len(all_additional_events)} additional events (Baptism, Education, Occupation, etc.)") print(f" - {len(all_additional_events)} additional events (Baptism, Education, Occupation, etc.)")
print(f" - Total events: {total_events}") print(f" - Total events: {total_events}")
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -1,182 +0,0 @@
#!/bin/bash
#
# Script to install MyTimeline plugin to snap-installed Gramps
#
# This script copies the MyTimeline plugin files to the Gramps snap plugin directory
# so it can be used in the snap-installed version of Gramps 6.0
#
set -euo pipefail # Exit on error, undefined vars, pipe failures
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source plugin files
PLUGIN_SRC_DIR="$SCRIPT_DIR"
PLUGIN_FILES=("MyTimeline.gpr.py" "MyTimeline.py")
# Target directory for snap-installed Gramps
# Gramps 6.0 plugins directory in snap
PLUGIN_DEST_DIR="$HOME/snap/gramps/current/.local/share/gramps/gramps60/plugins"
# Alternative locations to try (in case snap structure differs)
ALTERNATIVE_DIRS=(
"$HOME/snap/gramps/current/.local/share/gramps/gramps60/plugins"
"$HOME/snap/gramps/current/.gramps/gramps60/plugins"
"$HOME/snap/gramps/current/.gramps/plugins"
"$HOME/snap/gramps/common/.local/share/gramps/gramps60/plugins"
"$HOME/.local/share/gramps/gramps60/plugins"
"$HOME/.gramps/gramps60/plugins"
)
echo "=========================================="
echo "MyTimeline Plugin Installer for Snap Gramps"
echo "=========================================="
echo ""
# Function to check if Gramps is installed via snap
check_gramps_installed() {
if ! command -v gramps &> /dev/null && ! snap list gramps &> /dev/null; then
echo -e "${YELLOW}Warning: Gramps may not be installed via snap.${NC}"
echo "This script is designed for snap-installed Gramps."
echo "If Gramps is installed differently, you may need to manually install the plugin."
echo ""
read -p "Continue anyway? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Installation cancelled."
exit 0
fi
fi
}
# Function to find the correct plugin directory
find_plugin_dir() {
# Try the primary location first
if [ -d "$PLUGIN_DEST_DIR" ]; then
echo "$PLUGIN_DEST_DIR"
return 0
fi
# Try alternative locations
for dir in "${ALTERNATIVE_DIRS[@]}"; do
if [ -d "$dir" ]; then
echo "$dir"
return 0
fi
done
# If not found, ask user or create the primary location
echo ""
echo -e "${YELLOW}Warning: Plugin directory not found in standard locations.${NC}"
echo "Attempting to create: $PLUGIN_DEST_DIR"
mkdir -p "$PLUGIN_DEST_DIR"
echo "$PLUGIN_DEST_DIR"
return 0
}
# Check if Gramps is installed
check_gramps_installed
# Verify source files exist
echo "Checking source files..."
MISSING_FILES=()
for file in "${PLUGIN_FILES[@]}"; do
if [ ! -f "$PLUGIN_SRC_DIR/$file" ]; then
MISSING_FILES+=("$file")
else
echo -e "${GREEN}${NC} Found: $file"
fi
done
if [ ${#MISSING_FILES[@]} -gt 0 ]; then
echo ""
echo -e "${RED}Error: Missing required plugin files:${NC}"
for file in "${MISSING_FILES[@]}"; do
echo " - $file"
done
echo ""
echo -e "${YELLOW}Troubleshooting:${NC}"
echo " 1. Make sure you're running this script from the directory containing the plugin files"
echo " 2. Verify that MyTimeline.gpr.py and MyTimeline.py exist in: $PLUGIN_SRC_DIR"
echo " 3. Check file permissions: ls -la $PLUGIN_SRC_DIR"
exit 1
fi
# Find the target directory
echo ""
echo "Locating Gramps plugin directory..."
TARGET_DIR=$(find_plugin_dir)
if [ -z "$TARGET_DIR" ]; then
echo -e "${RED}Error: Could not determine plugin directory location.${NC}"
echo ""
echo -e "${YELLOW}Troubleshooting:${NC}"
echo " 1. Verify Gramps is installed: snap list gramps"
echo " 2. Check if snap directory exists: ls -la $HOME/snap/gramps/"
echo " 3. Try running Gramps at least once to create the plugin directory"
echo " 4. Manually create the directory: mkdir -p $PLUGIN_DEST_DIR"
echo ""
echo "If the issue persists, you may need to install the plugin manually."
exit 1
fi
echo -e "${GREEN}${NC} Target directory: $TARGET_DIR"
echo ""
# Check if plugin already exists
PLUGIN_EXISTS=false
for file in "${PLUGIN_FILES[@]}"; do
if [ -f "$TARGET_DIR/$file" ]; then
PLUGIN_EXISTS=true
break
fi
done
# Backup existing plugin if it exists
if [ "$PLUGIN_EXISTS" = true ]; then
echo -e "${YELLOW}Warning: Plugin files already exist in target directory.${NC}"
BACKUP_DIR="$TARGET_DIR/mytimeline_backup_$(date +%Y%m%d_%H%M%S)"
echo "Creating backup in: $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
for file in "${PLUGIN_FILES[@]}"; do
if [ -f "$TARGET_DIR/$file" ]; then
cp "$TARGET_DIR/$file" "$BACKUP_DIR/"
fi
done
echo -e "${GREEN}${NC} Backup created"
echo ""
fi
# Copy plugin files
echo "Installing plugin files..."
for file in "${PLUGIN_FILES[@]}"; do
if ! cp "$PLUGIN_SRC_DIR/$file" "$TARGET_DIR/"; then
echo -e "${RED}Error: Failed to copy $file${NC}"
echo "Check file permissions and disk space."
exit 1
fi
echo -e "${GREEN}${NC} Installed: $file"
done
echo ""
echo "=========================================="
echo -e "${GREEN}Installation completed successfully!${NC}"
echo "=========================================="
echo ""
echo "Plugin files installed to:"
echo " $TARGET_DIR"
echo ""
echo "Next steps:"
echo " 1. Restart Gramps if it's currently running"
echo " 2. Go to Edit → Preferences → Plugins"
echo " 3. Enable the 'MyTimeline' plugin"
echo " 4. The plugin should appear in the Views menu under 'Families'"
echo ""

View File

@ -1,165 +0,0 @@
#!/bin/bash
#
# Script to uninstall MyTimeline plugin from snap-installed Gramps
#
# This script removes the MyTimeline plugin files from the Gramps snap plugin directory
#
set -euo pipefail # Exit on error, undefined vars, pipe failures
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Plugin files to remove
PLUGIN_FILES=("MyTimeline.gpr.py" "MyTimeline.py")
# Target directory for snap-installed Gramps
PLUGIN_DEST_DIR="$HOME/snap/gramps/current/.local/share/gramps/gramps60/plugins"
# Alternative locations to try
ALTERNATIVE_DIRS=(
"$HOME/snap/gramps/current/.local/share/gramps/gramps60/plugins"
"$HOME/snap/gramps/current/.gramps/gramps60/plugins"
"$HOME/snap/gramps/current/.gramps/plugins"
"$HOME/snap/gramps/common/.local/share/gramps/gramps60/plugins"
"$HOME/.local/share/gramps/gramps60/plugins"
"$HOME/.gramps/gramps60/plugins"
)
echo "=========================================="
echo "MyTimeline Plugin Uninstaller for Snap Gramps"
echo "=========================================="
echo ""
# Function to find the correct plugin directory
find_plugin_dir() {
# Try the primary location first
if [ -d "$PLUGIN_DEST_DIR" ]; then
echo "$PLUGIN_DEST_DIR"
return 0
fi
# Try alternative locations
for dir in "${ALTERNATIVE_DIRS[@]}"; do
if [ -d "$dir" ]; then
echo "$dir"
return 0
fi
done
return 1
}
# Find the target directory
echo "Locating Gramps plugin directory..."
TARGET_DIR=$(find_plugin_dir)
if [ -z "$TARGET_DIR" ]; then
echo -e "${YELLOW}Warning: Plugin directory not found.${NC}"
echo "The plugin may already be uninstalled or Gramps may not be installed via snap."
echo ""
echo -e "${BLUE}Note:${NC} If you installed the plugin manually, you may need to remove it manually."
echo "Common locations to check:"
for dir in "${ALTERNATIVE_DIRS[@]}"; do
echo " - $dir"
done
exit 0
fi
echo -e "${GREEN}${NC} Target directory: $TARGET_DIR"
echo ""
# Check if plugin files exist
FILES_FOUND=0
FILES_REMOVED=0
for file in "${PLUGIN_FILES[@]}"; do
if [ -f "$TARGET_DIR/$file" ]; then
FILES_FOUND=$((FILES_FOUND + 1))
fi
done
if [ $FILES_FOUND -eq 0 ]; then
echo -e "${YELLOW}No plugin files found. The plugin may already be uninstalled.${NC}"
exit 0
fi
echo "Found $FILES_FOUND plugin file(s)."
echo ""
# Remove plugin files
echo "Removing plugin files..."
for file in "${PLUGIN_FILES[@]}"; do
if [ -f "$TARGET_DIR/$file" ]; then
if ! rm "$TARGET_DIR/$file"; then
echo -e "${RED}Error: Failed to remove $file${NC}"
echo "Check file permissions."
exit 1
fi
echo -e "${GREEN}${NC} Removed: $file"
FILES_REMOVED=$((FILES_REMOVED + 1))
fi
done
# Remove backup directories created by the installer
BACKUP_DIRS_REMOVED=0
echo ""
echo "Searching for backup directories..."
for backup_dir in "$TARGET_DIR"/mytimeline_backup_*; do
if [ -d "$backup_dir" ]; then
echo "Found backup directory: $(basename "$backup_dir")"
rm -rf "$backup_dir"
echo -e "${GREEN}${NC} Removed backup directory: $(basename "$backup_dir")"
BACKUP_DIRS_REMOVED=$((BACKUP_DIRS_REMOVED + 1))
fi
done
# Also check for any plugin files in subdirectories that might be loaded
echo ""
echo "Checking for plugin files in subdirectories..."
SUBDIR_FILES_REMOVED=0
for subdir in "$TARGET_DIR"/*; do
if [ -d "$subdir" ]; then
for file in "${PLUGIN_FILES[@]}"; do
if [ -f "$subdir/$file" ]; then
echo -e "${YELLOW}Warning: Found $file in subdirectory: $(basename "$subdir")${NC}"
rm -f "$subdir/$file"
echo -e "${GREEN}${NC} Removed: $(basename "$subdir")/$file"
SUBDIR_FILES_REMOVED=$((SUBDIR_FILES_REMOVED + 1))
fi
done
fi
done
TOTAL_REMOVED=$((FILES_REMOVED + BACKUP_DIRS_REMOVED + SUBDIR_FILES_REMOVED))
echo ""
echo "=========================================="
if [ $TOTAL_REMOVED -gt 0 ]; then
echo -e "${GREEN}Uninstallation completed successfully!${NC}"
echo "=========================================="
echo ""
echo "Removed:"
echo " - $FILES_REMOVED plugin file(s) from main directory"
if [ $BACKUP_DIRS_REMOVED -gt 0 ]; then
echo " - $BACKUP_DIRS_REMOVED backup directory/directories"
fi
if [ $SUBDIR_FILES_REMOVED -gt 0 ]; then
echo " - $SUBDIR_FILES_REMOVED plugin file(s) from subdirectories"
fi
echo ""
echo "Location: $TARGET_DIR"
echo ""
echo "Next steps:"
echo " 1. Restart Gramps if it's currently running"
echo " 2. The plugin should no longer appear in the Plugins menu"
else
echo -e "${YELLOW}No files or directories were removed.${NC}"
echo "=========================================="
fi
echo ""