From d3ea1705f4e3fd7f5ff5077a6c51a6dabb9f072c Mon Sep 17 00:00:00 2001 From: Przemek Wiech Date: Thu, 13 Feb 2020 14:33:43 +0100 Subject: [PATCH] Sort spouses by marriage date (#10) --- src/gedcom_util.spec.ts | 70 +++++++++++++++++++++++++++ src/gedcom_util.ts | 102 ++++++++++++++++++++++++++++++---------- src/wikitree.ts | 5 +- 3 files changed, 151 insertions(+), 26 deletions(-) create mode 100644 src/gedcom_util.spec.ts diff --git a/src/gedcom_util.spec.ts b/src/gedcom_util.spec.ts new file mode 100644 index 0000000..65ce071 --- /dev/null +++ b/src/gedcom_util.spec.ts @@ -0,0 +1,70 @@ +import {normalizeGedcom} from './gedcom_util'; + +describe('normalizeGedcom()', () => { + it('sorts children', () => { + const data = { + indis: [ + { + id: 'I3', + birth: {date: {year: 1901}}, + famc: 'F1', + }, + { + id: 'I2', + birth: {date: {year: 1902, month: 7}}, + famc: 'F1', + }, + { + id: 'I1', + birth: {date: {year: 1902, month: 8}}, + famc: 'F1', + }, + ], + fams: [ + { + id: 'F1', + children: ['I1', 'I2', 'I3'], + }, + ], + }; + const normalized = normalizeGedcom(data); + expect(normalized.fams[0].children).toEqual(['I3', 'I2', 'I1']); + }); + + it('sorts spouses', () => { + const data = { + indis: [ + {id: 'I1', fams: ['F1']}, + {id: 'I2', fams: ['F2']}, + {id: 'I3', fams: ['F3']}, + {id: 'I4', fams: ['F1', 'F2', 'F3']}, + ], + fams: [ + { + id: 'F3', + marriage: {date: {year: 1901}}, + husband: 'I4', + wife: 'I3', + }, + { + id: 'F2', + marriage: {date: {year: 1902, month: 7}}, + husband: 'I4', + wife: 'I2', + }, + { + id: 'F1', + marriage: {date: {year: 1902, month: 8}}, + husband: 'I4', + wife: 'I1', + }, + ], + }; + const normalized = normalizeGedcom(data); + expect(normalized.indis.find((i) => i.id === 'I4')!.fams).toEqual([ + 'F3', + 'F2', + 'F1', + ]); + }); +}); diff --git a/src/gedcom_util.ts b/src/gedcom_util.ts index 95dd173..5c48cad 100644 --- a/src/gedcom_util.ts +++ b/src/gedcom_util.ts @@ -4,6 +4,7 @@ import { JsonIndi, gedcomEntriesToJson, JsonImage, + JsonEvent, } from 'topola'; import {GedcomEntry, parse as parseGedcom} from 'parse-gedcom'; @@ -58,6 +59,33 @@ function strcmp(a: string, b: string) { return 0; } +/** Compares dates of the given events. */ +function compareDates( + event1: JsonEvent | undefined, + event2: JsonEvent | undefined, +): number { + const date1 = + event1 && (event1.date || (event1.dateRange && event1.dateRange.from)); + const date2 = + event2 && (event2.date || (event2.dateRange && event2.dateRange.from)); + if (!date1 || !date1.year || !date2 || !date2.year) { + return 0; + } + if (date1.year !== date2.year) { + return date1.year - date2.year; + } + if (!date1.month || !date2.month) { + return 0; + } + if (date1.month !== date2.month) { + return date1.month - date2.month; + } + if (date1.day && date2.day && date1.day !== date2.day) { + return date1.month - date2.month; + } + return 0; +} + /** Birth date comparator for individuals. */ function birthDatesComparator(gedcom: JsonGedcomData) { const idToIndiMap = new Map(); @@ -66,31 +94,29 @@ function birthDatesComparator(gedcom: JsonGedcomData) { }); return (indiId1: string, indiId2: string) => { - const idComparison = strcmp(indiId1, indiId2); const indi1: JsonIndi = idToIndiMap[indiId1]; const indi2: JsonIndi = idToIndiMap[indiId2]; - const birth1 = indi1 && indi1.birth; - const birth2 = indi2 && indi2.birth; - const date1 = - birth1 && (birth1.date || (birth1.dateRange && birth1.dateRange.from)); - const date2 = - birth2 && (birth2.date || (birth2.dateRange && birth2.dateRange.from)); - if (!date1 || !date1.year || !date2 || !date2.year) { - return idComparison; - } - if (date1.year !== date2.year) { - return date1.year - date2.year; - } - if (!date1.month || !date2.month) { - return idComparison; - } - if (date1.month !== date2.month) { - return date1.month - date2.month; - } - if (date1.day && date2.day && date1.day !== date2.day) { - return date1.month - date2.month; - } - return idComparison; + return ( + compareDates(indi1 && indi1.birth, indi2 && indi2.birth) || + strcmp(indiId1, indiId2) + ); + }; +} + +/** Marriage date comparator for families. */ +function marriageDatesComparator(gedcom: JsonGedcomData) { + const idToFamMap = new Map(); + gedcom.fams.forEach((fam) => { + idToFamMap[fam.id] = fam; + }); + + return (famId1: string, famId2: string) => { + const fam1: JsonFam = idToFamMap[famId1]; + const fam2: JsonFam = idToFamMap[famId2]; + return ( + compareDates(fam1 && fam1.marriage, fam2 && fam2.marriage) || + strcmp(famId1, famId2) + ); }; } @@ -119,6 +145,34 @@ function sortChildren(gedcom: JsonGedcomData): JsonGedcomData { return Object.assign({}, gedcom, {fams: newFams}); } +/** + * Sorts spouses by marriage date. + * Does not modify the input objects. + */ +function sortIndiSpouses( + indi: JsonIndi, + comparator: (id1: string, id2: string) => number, +): JsonFam { + if (!indi.fams) { + return indi; + } + const newFams = indi.fams.sort(comparator); + return Object.assign({}, indi, {fams: newFams}); +} + +function sortSpouses(gedcom: JsonGedcomData): JsonGedcomData { + const comparator = marriageDatesComparator(gedcom); + const newIndis = gedcom.indis.map((indi) => + sortIndiSpouses(indi, comparator), + ); + return Object.assign({}, gedcom, {indis: newIndis}); +} + +/** Sorts children and spouses. */ +export function normalizeGedcom(gedcom: JsonGedcomData): JsonGedcomData { + return sortSpouses(sortChildren(gedcom)); +} + const IMAGE_EXTENSIONS = ['.jpg', '.png', '.gif']; /** Returns true if the given file name has a known image extension. */ @@ -185,7 +239,7 @@ export function convertGedcom( } return { - chartData: filterImages(sortChildren(json), images), + chartData: filterImages(normalizeGedcom(json), images), gedcom: prepareGedcom(entries), }; } diff --git a/src/wikitree.ts b/src/wikitree.ts index ac24c85..4c31784 100644 --- a/src/wikitree.ts +++ b/src/wikitree.ts @@ -1,6 +1,6 @@ import Cookies from 'js-cookie'; import {Date, JsonFam, JsonIndi} from 'topola'; -import {GedcomData, TopolaData} from './gedcom_util'; +import {GedcomData, TopolaData, normalizeGedcom} from './gedcom_util'; import {GedcomEntry} from 'parse-gedcom'; /** WikiTree API getAncestors request. */ @@ -286,8 +286,9 @@ export async function loadWikiTree( return fam; }); + const chartData = normalizeGedcom({indis, fams}); const gedcom = buildGedcom(indis); - return {chartData: {indis, fams}, gedcom}; + return {chartData, gedcom}; } /** Creates a family identifier given 2 spouse identifiers. */