@treeviz/gedcom-parser 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/classes/index.d.ts +2 -28
- package/dist/classes/index.js +6713 -21
- package/dist/classes/index.js.map +1 -0
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +7826 -24
- package/dist/cli/index.js.map +1 -0
- package/dist/constants/index.d.ts +49 -4
- package/dist/constants/index.js +5797 -4
- package/dist/constants/index.js.map +1 -0
- package/dist/factories/index.d.ts +202 -13
- package/dist/factories/index.js +5782 -8
- package/dist/factories/index.js.map +1 -0
- package/dist/index-BvW-KWJP.d.ts +1654 -0
- package/dist/index.d.ts +9 -28
- package/dist/index.js +7380 -35
- package/dist/index.js.map +1 -0
- package/dist/interfaces/index.d.ts +2 -12
- package/dist/interfaces/index.js +3 -1
- package/dist/interfaces/index.js.map +1 -0
- package/dist/kinship-translator/index.d.ts +146 -11
- package/dist/kinship-translator/index.js +5727 -9
- package/dist/kinship-translator/index.js.map +1 -0
- package/dist/place-parser-RlXCXZ8F.d.ts +37 -0
- package/dist/structures/index.d.ts +25 -27
- package/dist/structures/index.js +3 -1
- package/dist/structures/index.js.map +1 -0
- package/dist/types/index.d.ts +69 -8
- package/dist/types/index.js +31 -6
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.d.ts +91 -18
- package/dist/utils/index.js +6953 -17
- package/dist/utils/index.js.map +1 -0
- package/package.json +4 -3
- package/dist/classes/common.d.ts +0 -91
- package/dist/classes/common.d.ts.map +0 -1
- package/dist/classes/common.js +0 -488
- package/dist/classes/date.d.ts +0 -27
- package/dist/classes/date.d.ts.map +0 -1
- package/dist/classes/date.js +0 -178
- package/dist/classes/fam.d.ts +0 -19
- package/dist/classes/fam.d.ts.map +0 -1
- package/dist/classes/fam.js +0 -38
- package/dist/classes/fams.d.ts +0 -16
- package/dist/classes/fams.d.ts.map +0 -1
- package/dist/classes/fams.js +0 -41
- package/dist/classes/gedcom.d.ts +0 -139
- package/dist/classes/gedcom.d.ts.map +0 -1
- package/dist/classes/gedcom.js +0 -910
- package/dist/classes/index.d.ts.map +0 -1
- package/dist/classes/indi.d.ts +0 -323
- package/dist/classes/indi.d.ts.map +0 -1
- package/dist/classes/indi.js +0 -2131
- package/dist/classes/indis.d.ts +0 -55
- package/dist/classes/indis.d.ts.map +0 -1
- package/dist/classes/indis.js +0 -457
- package/dist/classes/list.d.ts +0 -55
- package/dist/classes/list.d.ts.map +0 -1
- package/dist/classes/list.js +0 -440
- package/dist/classes/name.d.ts +0 -20
- package/dist/classes/name.d.ts.map +0 -1
- package/dist/classes/name.js +0 -48
- package/dist/classes/note.d.ts +0 -12
- package/dist/classes/note.d.ts.map +0 -1
- package/dist/classes/note.js +0 -46
- package/dist/classes/obje.d.ts +0 -12
- package/dist/classes/obje.d.ts.map +0 -1
- package/dist/classes/obje.js +0 -81
- package/dist/classes/objes.d.ts +0 -8
- package/dist/classes/objes.d.ts.map +0 -1
- package/dist/classes/objes.js +0 -13
- package/dist/classes/repo.d.ts +0 -11
- package/dist/classes/repo.d.ts.map +0 -1
- package/dist/classes/repo.js +0 -6
- package/dist/classes/repos.d.ts +0 -8
- package/dist/classes/repos.d.ts.map +0 -1
- package/dist/classes/repos.js +0 -13
- package/dist/classes/sour.d.ts +0 -11
- package/dist/classes/sour.d.ts.map +0 -1
- package/dist/classes/sour.js +0 -6
- package/dist/classes/sours.d.ts +0 -8
- package/dist/classes/sours.d.ts.map +0 -1
- package/dist/classes/sours.js +0 -13
- package/dist/classes/subm.d.ts +0 -10
- package/dist/classes/subm.d.ts.map +0 -1
- package/dist/classes/subm.js +0 -6
- package/dist/classes/subms.d.ts +0 -8
- package/dist/classes/subms.d.ts.map +0 -1
- package/dist/classes/subms.js +0 -13
- package/dist/cli/commands/convert.d.ts +0 -3
- package/dist/cli/commands/convert.d.ts.map +0 -1
- package/dist/cli/commands/convert.js +0 -83
- package/dist/cli/commands/extract.d.ts +0 -3
- package/dist/cli/commands/extract.d.ts.map +0 -1
- package/dist/cli/commands/extract.js +0 -85
- package/dist/cli/commands/find.d.ts +0 -3
- package/dist/cli/commands/find.d.ts.map +0 -1
- package/dist/cli/commands/find.js +0 -97
- package/dist/cli/commands/info.d.ts +0 -3
- package/dist/cli/commands/info.d.ts.map +0 -1
- package/dist/cli/commands/info.js +0 -80
- package/dist/cli/commands/merge.d.ts +0 -3
- package/dist/cli/commands/merge.d.ts.map +0 -1
- package/dist/cli/commands/merge.js +0 -61
- package/dist/cli/commands/relatives.d.ts +0 -3
- package/dist/cli/commands/relatives.d.ts.map +0 -1
- package/dist/cli/commands/relatives.js +0 -107
- package/dist/cli/commands/show.d.ts +0 -3
- package/dist/cli/commands/show.d.ts.map +0 -1
- package/dist/cli/commands/show.js +0 -176
- package/dist/cli/commands/stats.d.ts +0 -3
- package/dist/cli/commands/stats.d.ts.map +0 -1
- package/dist/cli/commands/stats.js +0 -59
- package/dist/cli/commands/validate.d.ts +0 -3
- package/dist/cli/commands/validate.d.ts.map +0 -1
- package/dist/cli/commands/validate.js +0 -148
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/utils/formatters.d.ts +0 -69
- package/dist/cli/utils/formatters.d.ts.map +0 -1
- package/dist/cli/utils/formatters.js +0 -125
- package/dist/cli/utils/helpers.d.ts +0 -21
- package/dist/cli/utils/helpers.d.ts.map +0 -1
- package/dist/cli/utils/helpers.js +0 -58
- package/dist/constants/constants.d.ts +0 -14
- package/dist/constants/constants.d.ts.map +0 -1
- package/dist/constants/constants.js +0 -13
- package/dist/constants/filters.d.ts +0 -18
- package/dist/constants/filters.d.ts.map +0 -1
- package/dist/constants/filters.js +0 -44
- package/dist/constants/index.d.ts.map +0 -1
- package/dist/constants/orders.d.ts +0 -17
- package/dist/constants/orders.d.ts.map +0 -1
- package/dist/constants/orders.js +0 -240
- package/dist/factories/cache-factory.d.ts +0 -30
- package/dist/factories/cache-factory.d.ts.map +0 -1
- package/dist/factories/cache-factory.js +0 -43
- package/dist/factories/date-locale-factory.d.ts +0 -30
- package/dist/factories/date-locale-factory.d.ts.map +0 -1
- package/dist/factories/date-locale-factory.js +0 -34
- package/dist/factories/i18n-factory.d.ts +0 -40
- package/dist/factories/i18n-factory.d.ts.map +0 -1
- package/dist/factories/i18n-factory.js +0 -44
- package/dist/factories/index.d.ts.map +0 -1
- package/dist/factories/kinship-factory.d.ts +0 -38
- package/dist/factories/kinship-factory.d.ts.map +0 -1
- package/dist/factories/kinship-factory.js +0 -35
- package/dist/factories/place-parser-provider.d.ts +0 -32
- package/dist/factories/place-parser-provider.d.ts.map +0 -1
- package/dist/factories/place-parser-provider.js +0 -35
- package/dist/factories/place-translator-provider.d.ts +0 -32
- package/dist/factories/place-translator-provider.d.ts.map +0 -1
- package/dist/factories/place-translator-provider.js +0 -35
- package/dist/index.d.ts.map +0 -1
- package/dist/interfaces/common.d.ts +0 -43
- package/dist/interfaces/common.d.ts.map +0 -1
- package/dist/interfaces/common.js +0 -1
- package/dist/interfaces/fam.d.ts +0 -11
- package/dist/interfaces/fam.d.ts.map +0 -1
- package/dist/interfaces/fam.js +0 -1
- package/dist/interfaces/fams.d.ts +0 -9
- package/dist/interfaces/fams.d.ts.map +0 -1
- package/dist/interfaces/fams.js +0 -1
- package/dist/interfaces/gedcom.d.ts +0 -30
- package/dist/interfaces/gedcom.d.ts.map +0 -1
- package/dist/interfaces/gedcom.js +0 -1
- package/dist/interfaces/index.d.ts.map +0 -1
- package/dist/interfaces/indi.d.ts +0 -113
- package/dist/interfaces/indi.d.ts.map +0 -1
- package/dist/interfaces/indi.js +0 -1
- package/dist/interfaces/indis.d.ts +0 -31
- package/dist/interfaces/indis.d.ts.map +0 -1
- package/dist/interfaces/indis.js +0 -1
- package/dist/interfaces/list.d.ts +0 -52
- package/dist/interfaces/list.d.ts.map +0 -1
- package/dist/interfaces/list.js +0 -1
- package/dist/interfaces/obje.d.ts +0 -7
- package/dist/interfaces/obje.d.ts.map +0 -1
- package/dist/interfaces/obje.js +0 -1
- package/dist/interfaces/repo.d.ts +0 -7
- package/dist/interfaces/repo.d.ts.map +0 -1
- package/dist/interfaces/repo.js +0 -1
- package/dist/interfaces/sour.d.ts +0 -7
- package/dist/interfaces/sour.d.ts.map +0 -1
- package/dist/interfaces/sour.js +0 -1
- package/dist/interfaces/subm.d.ts +0 -6
- package/dist/interfaces/subm.d.ts.map +0 -1
- package/dist/interfaces/subm.js +0 -1
- package/dist/kinship-translator/index.d.ts.map +0 -1
- package/dist/kinship-translator/kinship-translator.basic.d.ts +0 -30
- package/dist/kinship-translator/kinship-translator.basic.d.ts.map +0 -1
- package/dist/kinship-translator/kinship-translator.basic.js +0 -74
- package/dist/kinship-translator/kinship-translator.d.ts +0 -26
- package/dist/kinship-translator/kinship-translator.d.ts.map +0 -1
- package/dist/kinship-translator/kinship-translator.de.d.ts +0 -18
- package/dist/kinship-translator/kinship-translator.de.d.ts.map +0 -1
- package/dist/kinship-translator/kinship-translator.de.js +0 -180
- package/dist/kinship-translator/kinship-translator.en.d.ts +0 -18
- package/dist/kinship-translator/kinship-translator.en.d.ts.map +0 -1
- package/dist/kinship-translator/kinship-translator.en.js +0 -182
- package/dist/kinship-translator/kinship-translator.es.d.ts +0 -18
- package/dist/kinship-translator/kinship-translator.es.d.ts.map +0 -1
- package/dist/kinship-translator/kinship-translator.es.js +0 -181
- package/dist/kinship-translator/kinship-translator.fr.d.ts +0 -18
- package/dist/kinship-translator/kinship-translator.fr.d.ts.map +0 -1
- package/dist/kinship-translator/kinship-translator.fr.js +0 -181
- package/dist/kinship-translator/kinship-translator.hu.d.ts +0 -19
- package/dist/kinship-translator/kinship-translator.hu.d.ts.map +0 -1
- package/dist/kinship-translator/kinship-translator.hu.js +0 -226
- package/dist/kinship-translator/kinship-translator.interface.d.ts +0 -19
- package/dist/kinship-translator/kinship-translator.interface.d.ts.map +0 -1
- package/dist/kinship-translator/kinship-translator.interface.js +0 -1
- package/dist/kinship-translator/kinship-translator.js +0 -103
- package/dist/kinship-translator/patterns.de.d.ts +0 -2
- package/dist/kinship-translator/patterns.de.d.ts.map +0 -1
- package/dist/kinship-translator/patterns.de.js +0 -14
- package/dist/kinship-translator/patterns.en.d.ts +0 -2
- package/dist/kinship-translator/patterns.en.d.ts.map +0 -1
- package/dist/kinship-translator/patterns.en.js +0 -14
- package/dist/kinship-translator/patterns.es.d.ts +0 -2
- package/dist/kinship-translator/patterns.es.d.ts.map +0 -1
- package/dist/kinship-translator/patterns.es.js +0 -14
- package/dist/kinship-translator/patterns.fr.d.ts +0 -2
- package/dist/kinship-translator/patterns.fr.d.ts.map +0 -1
- package/dist/kinship-translator/patterns.fr.js +0 -14
- package/dist/kinship-translator/patterns.hu.d.ts +0 -6
- package/dist/kinship-translator/patterns.hu.d.ts.map +0 -1
- package/dist/kinship-translator/patterns.hu.js +0 -97
- package/dist/kinship-translator/translators.d.ts +0 -6
- package/dist/kinship-translator/translators.d.ts.map +0 -1
- package/dist/kinship-translator/translators.js +0 -5
- package/dist/kinship-translator/types.d.ts +0 -9
- package/dist/kinship-translator/types.d.ts.map +0 -1
- package/dist/kinship-translator/types.js +0 -1
- package/dist/structures/address.d.ts +0 -14
- package/dist/structures/address.d.ts.map +0 -1
- package/dist/structures/address.js +0 -1
- package/dist/structures/association.d.ts +0 -13
- package/dist/structures/association.d.ts.map +0 -1
- package/dist/structures/association.js +0 -1
- package/dist/structures/change-date.d.ts +0 -8
- package/dist/structures/change-date.d.ts.map +0 -1
- package/dist/structures/change-date.js +0 -1
- package/dist/structures/creation-date.d.ts +0 -7
- package/dist/structures/creation-date.d.ts.map +0 -1
- package/dist/structures/creation-date.js +0 -1
- package/dist/structures/date.d.ts +0 -9
- package/dist/structures/date.d.ts.map +0 -1
- package/dist/structures/date.js +0 -1
- package/dist/structures/event-detail-structure.d.ts +0 -25
- package/dist/structures/event-detail-structure.d.ts.map +0 -1
- package/dist/structures/event-detail-structure.js +0 -1
- package/dist/structures/family.d.ts +0 -34
- package/dist/structures/family.d.ts.map +0 -1
- package/dist/structures/family.js +0 -1
- package/dist/structures/gedcom.d.ts +0 -59
- package/dist/structures/gedcom.d.ts.map +0 -1
- package/dist/structures/gedcom.js +0 -1
- package/dist/structures/index.d.ts.map +0 -1
- package/dist/structures/individual-event-detail-structure.d.ts +0 -9
- package/dist/structures/individual-event-detail-structure.d.ts.map +0 -1
- package/dist/structures/individual-event-detail-structure.js +0 -1
- package/dist/structures/individual-event-structure.d.ts +0 -83
- package/dist/structures/individual-event-structure.d.ts.map +0 -1
- package/dist/structures/individual-event-structure.js +0 -1
- package/dist/structures/individual.d.ts +0 -40
- package/dist/structures/individual.d.ts.map +0 -1
- package/dist/structures/individual.js +0 -1
- package/dist/structures/lds-ordinance-detail.d.ts +0 -17
- package/dist/structures/lds-ordinance-detail.d.ts.map +0 -1
- package/dist/structures/lds-ordinance-detail.js +0 -1
- package/dist/structures/lds-spouse-sealing.d.ts +0 -7
- package/dist/structures/lds-spouse-sealing.d.ts.map +0 -1
- package/dist/structures/lds-spouse-sealing.js +0 -1
- package/dist/structures/marriage-date.d.ts +0 -11
- package/dist/structures/marriage-date.d.ts.map +0 -1
- package/dist/structures/marriage-date.js +0 -1
- package/dist/structures/multimedia-link.d.ts +0 -16
- package/dist/structures/multimedia-link.d.ts.map +0 -1
- package/dist/structures/multimedia-link.js +0 -1
- package/dist/structures/non-event.d.ts +0 -12
- package/dist/structures/non-event.d.ts.map +0 -1
- package/dist/structures/non-event.js +0 -1
- package/dist/structures/note.d.ts +0 -16
- package/dist/structures/note.d.ts.map +0 -1
- package/dist/structures/note.js +0 -1
- package/dist/structures/personal-name-pieces.d.ts +0 -11
- package/dist/structures/personal-name-pieces.d.ts.map +0 -1
- package/dist/structures/personal-name-pieces.js +0 -1
- package/dist/structures/personal-name.d.ts +0 -16
- package/dist/structures/personal-name.d.ts.map +0 -1
- package/dist/structures/personal-name.js +0 -1
- package/dist/structures/place.d.ts +0 -20
- package/dist/structures/place.d.ts.map +0 -1
- package/dist/structures/place.js +0 -1
- package/dist/structures/repository.d.ts +0 -6
- package/dist/structures/repository.d.ts.map +0 -1
- package/dist/structures/repository.js +0 -1
- package/dist/structures/source-citation.d.ts +0 -27
- package/dist/structures/source-citation.d.ts.map +0 -1
- package/dist/structures/source-citation.js +0 -1
- package/dist/structures/source-repository-citation.d.ts +0 -13
- package/dist/structures/source-repository-citation.d.ts.map +0 -1
- package/dist/structures/source-repository-citation.js +0 -1
- package/dist/structures/source.d.ts +0 -28
- package/dist/structures/source.d.ts.map +0 -1
- package/dist/structures/source.js +0 -1
- package/dist/types/ancestry-media.d.ts +0 -65
- package/dist/types/ancestry-media.d.ts.map +0 -1
- package/dist/types/ancestry-media.js +0 -1
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/settings.d.ts +0 -12
- package/dist/types/settings.d.ts.map +0 -1
- package/dist/types/settings.js +0 -1
- package/dist/types/types.d.ts +0 -225
- package/dist/types/types.d.ts.map +0 -1
- package/dist/types/types.js +0 -26
- package/dist/utils/cache.d.ts +0 -22
- package/dist/utils/cache.d.ts.map +0 -1
- package/dist/utils/cache.js +0 -57
- package/dist/utils/common-creator.d.ts +0 -13
- package/dist/utils/common-creator.d.ts.map +0 -1
- package/dist/utils/common-creator.js +0 -72
- package/dist/utils/date-formatter.d.ts +0 -35
- package/dist/utils/date-formatter.d.ts.map +0 -1
- package/dist/utils/date-formatter.js +0 -207
- package/dist/utils/get-all-prop.d.ts +0 -4
- package/dist/utils/get-all-prop.d.ts.map +0 -1
- package/dist/utils/get-all-prop.js +0 -5
- package/dist/utils/get-family-with.d.ts +0 -5
- package/dist/utils/get-family-with.d.ts.map +0 -1
- package/dist/utils/get-family-with.js +0 -11
- package/dist/utils/get-places.d.ts +0 -21
- package/dist/utils/get-places.d.ts.map +0 -1
- package/dist/utils/get-places.js +0 -77
- package/dist/utils/get-product-details.d.ts +0 -4
- package/dist/utils/get-product-details.d.ts.map +0 -1
- package/dist/utils/get-product-details.js +0 -6
- package/dist/utils/get-raw-size.d.ts +0 -2
- package/dist/utils/get-raw-size.d.ts.map +0 -1
- package/dist/utils/get-raw-size.js +0 -3
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/logger.d.ts +0 -3
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -11
- package/dist/utils/name-formatter.d.ts +0 -10
- package/dist/utils/name-formatter.d.ts.map +0 -1
- package/dist/utils/name-formatter.js +0 -45
- package/dist/utils/nested-group.d.ts +0 -4
- package/dist/utils/nested-group.d.ts.map +0 -1
- package/dist/utils/nested-group.js +0 -34
- package/dist/utils/ordinalize.d.ts +0 -3
- package/dist/utils/ordinalize.d.ts.map +0 -1
- package/dist/utils/ordinalize.js +0 -104
- package/dist/utils/parser.d.ts +0 -17
- package/dist/utils/parser.d.ts.map +0 -1
- package/dist/utils/parser.js +0 -321
- package/dist/utils/place-parser.d.ts +0 -16
- package/dist/utils/place-parser.d.ts.map +0 -1
- package/dist/utils/place-parser.js +0 -46
- package/dist/utils/place-translator.d.ts +0 -6
- package/dist/utils/place-translator.d.ts.map +0 -1
- package/dist/utils/place-translator.js +0 -8
- package/dist/utils/place-types.d.ts +0 -27
- package/dist/utils/place-types.d.ts.map +0 -1
- package/dist/utils/place-types.js +0 -14
- package/dist/utils/range.d.ts +0 -19
- package/dist/utils/range.d.ts.map +0 -1
- package/dist/utils/range.js +0 -265
package/dist/classes/gedcom.js
DELETED
|
@@ -1,910 +0,0 @@
|
|
|
1
|
-
import { getVersion } from "../utils/get-product-details.js";
|
|
2
|
-
import { Common, createCommon } from "./common.js";
|
|
3
|
-
import { CustomTags } from "./indi.js";
|
|
4
|
-
import { List } from "./list.js";
|
|
5
|
-
export class GedCom extends Common {
|
|
6
|
-
constructor() {
|
|
7
|
-
super();
|
|
8
|
-
this.tagMembers = {};
|
|
9
|
-
this.reflist = {};
|
|
10
|
-
this.refcount = 0;
|
|
11
|
-
delete this._gedcom;
|
|
12
|
-
delete this.id;
|
|
13
|
-
this.removeValue();
|
|
14
|
-
}
|
|
15
|
-
getMain(type, index) {
|
|
16
|
-
const list = !type || type instanceof List ? type : this.getList(type);
|
|
17
|
-
if (!list) {
|
|
18
|
-
return undefined;
|
|
19
|
-
}
|
|
20
|
-
if (typeof index === "string") {
|
|
21
|
-
return list.item(index);
|
|
22
|
-
}
|
|
23
|
-
// const keyProbe: IdType[] = [
|
|
24
|
-
// `@I${index}@`,
|
|
25
|
-
// `@P${index}@`,
|
|
26
|
-
// `@XI${index}@`,
|
|
27
|
-
// `@XXI${index}@`,
|
|
28
|
-
// ];
|
|
29
|
-
// let itemProbe: T | undefined;
|
|
30
|
-
// keyProbe.find((key) => {
|
|
31
|
-
// const i = list.item(key);
|
|
32
|
-
// if (i) {
|
|
33
|
-
// itemProbe = i as T;
|
|
34
|
-
// return true;
|
|
35
|
-
// }
|
|
36
|
-
// return false;
|
|
37
|
-
// });
|
|
38
|
-
// if (itemProbe) {
|
|
39
|
-
// return itemProbe as T;
|
|
40
|
-
// }
|
|
41
|
-
const keys = list.keys();
|
|
42
|
-
return list.item(keys[index]);
|
|
43
|
-
}
|
|
44
|
-
getList(type) {
|
|
45
|
-
return this.get(type);
|
|
46
|
-
}
|
|
47
|
-
indis() {
|
|
48
|
-
return this.getList("@@INDI");
|
|
49
|
-
}
|
|
50
|
-
cloneIndis(target, source, avoidKeys = [], removeFromOriginalList = true) {
|
|
51
|
-
const hasFAMS = avoidKeys.includes("FAMS");
|
|
52
|
-
const targetIndi = typeof target === "string" ? this.indi(target) : target;
|
|
53
|
-
const sourceIndi = typeof source === "string" ? this.indi(source) : source;
|
|
54
|
-
if (!targetIndi || !sourceIndi) {
|
|
55
|
-
return this;
|
|
56
|
-
}
|
|
57
|
-
const spousesFamily = hasFAMS ? sourceIndi.FAMS?.toList() : undefined;
|
|
58
|
-
const childrenFamily = sourceIndi.FAMC?.toList();
|
|
59
|
-
const cloned = targetIndi.clone(false, avoidKeys);
|
|
60
|
-
const newCloned = sourceIndi.merge(cloned);
|
|
61
|
-
newCloned.cloneOf = targetIndi.id;
|
|
62
|
-
targetIndi.clonedBy = newCloned.id;
|
|
63
|
-
if (newCloned.id) {
|
|
64
|
-
this.getList("@@INDI")?.item(newCloned.id, newCloned);
|
|
65
|
-
if (childrenFamily?.length) {
|
|
66
|
-
childrenFamily.forEach((fam) => {
|
|
67
|
-
if (!fam.ref) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const newChild = createCommon(this, undefined, fam.ref);
|
|
71
|
-
newChild.value = newCloned.id;
|
|
72
|
-
fam.ref.assign("CHIL", newChild, true);
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
if (spousesFamily?.length) {
|
|
76
|
-
spousesFamily.forEach((fam) => {
|
|
77
|
-
if (!fam.ref) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
const newSpouse = createCommon(this, undefined, fam.ref);
|
|
81
|
-
newSpouse.value = newCloned.id;
|
|
82
|
-
if (newCloned.isMale()) {
|
|
83
|
-
fam.ref.assign("HUSB", newSpouse, true);
|
|
84
|
-
}
|
|
85
|
-
else if (newCloned.isFemale()) {
|
|
86
|
-
fam.ref.assign("WIFE", newSpouse, true);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
if (removeFromOriginalList &&
|
|
91
|
-
newCloned.type &&
|
|
92
|
-
newCloned.type !== targetIndi.type) {
|
|
93
|
-
this.getList(`@@${newCloned.type}`)?.removeItem(newCloned.id);
|
|
94
|
-
}
|
|
95
|
-
newCloned.type = "INDI";
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
mergeIndis(target, source, removeFromOriginalList = true) {
|
|
99
|
-
const targetIndi = typeof target === "string" ? this.indi(target) : target;
|
|
100
|
-
const sourceIndi = typeof source === "string" ? this.indi(source) : source;
|
|
101
|
-
if (!targetIndi || !sourceIndi) {
|
|
102
|
-
return this;
|
|
103
|
-
}
|
|
104
|
-
const sourceIndiId = sourceIndi.id;
|
|
105
|
-
const spousesFamily = sourceIndi.FAMS?.toList();
|
|
106
|
-
const childrenFamily = sourceIndi.FAMC?.toList();
|
|
107
|
-
const newLinked = targetIndi.merge(sourceIndi);
|
|
108
|
-
const mergedId = newLinked.id;
|
|
109
|
-
if (sourceIndiId && (spousesFamily?.length || childrenFamily?.length)) {
|
|
110
|
-
if (childrenFamily?.length && mergedId) {
|
|
111
|
-
childrenFamily.forEach((fam) => {
|
|
112
|
-
if (!fam.ref) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const newChild = createCommon(this, undefined, fam.ref);
|
|
116
|
-
newChild.value = mergedId;
|
|
117
|
-
fam.ref.assign("CHIL", newChild, true);
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
if (spousesFamily?.length && mergedId) {
|
|
121
|
-
spousesFamily.forEach((fam) => {
|
|
122
|
-
if (!fam.ref) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const newSpouse = createCommon(this, undefined, fam.ref);
|
|
126
|
-
newSpouse.value = mergedId;
|
|
127
|
-
if (newLinked?.isMale()) {
|
|
128
|
-
fam.ref.assign("HUSB", newSpouse, true);
|
|
129
|
-
}
|
|
130
|
-
else if (newLinked?.isFemale()) {
|
|
131
|
-
fam.ref.assign("WIFE", newSpouse, true);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (removeFromOriginalList && sourceIndi?.type && sourceIndi.id) {
|
|
137
|
-
this.getList(`@@${sourceIndi.type}`)?.removeItem(sourceIndi.id);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
fams() {
|
|
141
|
-
return this.getList("@@FAM");
|
|
142
|
-
}
|
|
143
|
-
objes() {
|
|
144
|
-
return this.getList("@@OBJE");
|
|
145
|
-
}
|
|
146
|
-
sours() {
|
|
147
|
-
return this.getList("@@SOUR");
|
|
148
|
-
}
|
|
149
|
-
repos() {
|
|
150
|
-
return this.getList("@@REPO");
|
|
151
|
-
}
|
|
152
|
-
subms() {
|
|
153
|
-
return this.getList("@@SUBM");
|
|
154
|
-
}
|
|
155
|
-
tags() {
|
|
156
|
-
return this.getList("@@_MTTAG");
|
|
157
|
-
}
|
|
158
|
-
customTags() {
|
|
159
|
-
return this.getList("@@_MTTAG")?.filter((tag) => {
|
|
160
|
-
return tag?.get("_MTCAT.NAME")?.toValue() === "Custom";
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
indi(index) {
|
|
164
|
-
return this.getMain(this.indis(), index);
|
|
165
|
-
}
|
|
166
|
-
fam(index) {
|
|
167
|
-
return this.getMain(this.fams(), index);
|
|
168
|
-
}
|
|
169
|
-
obje(index) {
|
|
170
|
-
return this.getMain(this.objes(), index);
|
|
171
|
-
}
|
|
172
|
-
sour(index) {
|
|
173
|
-
return this.getMain(this.sours(), index);
|
|
174
|
-
}
|
|
175
|
-
repo(index) {
|
|
176
|
-
return this.getMain(this.repos(), index);
|
|
177
|
-
}
|
|
178
|
-
subm(index) {
|
|
179
|
-
return this.getMain(this.subms(), index);
|
|
180
|
-
}
|
|
181
|
-
tag(index) {
|
|
182
|
-
return this.getMain(this.tags(), index);
|
|
183
|
-
}
|
|
184
|
-
fromList(id) {
|
|
185
|
-
return id ? this.reflist?.[id] : undefined;
|
|
186
|
-
}
|
|
187
|
-
tagByName(name) {
|
|
188
|
-
return this.tags()?.find((tag) => {
|
|
189
|
-
const tagName = tag?.get("NAME")?.toValue();
|
|
190
|
-
return tagName === name;
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
customTag(index) {
|
|
194
|
-
return this.getMain(this.customTags(), index);
|
|
195
|
-
}
|
|
196
|
-
getIndiRelatedLists(indis) {
|
|
197
|
-
const refs = {};
|
|
198
|
-
const individuals = this.indis()?.filter((indi, indiKey) => {
|
|
199
|
-
if (indis.includes(indiKey)) {
|
|
200
|
-
indi.getRefs()
|
|
201
|
-
?.values()
|
|
202
|
-
?.forEach((ref) => {
|
|
203
|
-
const refKey = ref?.value;
|
|
204
|
-
if (ref && refKey && !refs[refKey]) {
|
|
205
|
-
refs[refKey] = ref;
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
return true;
|
|
209
|
-
}
|
|
210
|
-
return false;
|
|
211
|
-
});
|
|
212
|
-
const usedLists = Object.entries(this).filter(([prop, list]) => {
|
|
213
|
-
return (prop !== "@@INDI" &&
|
|
214
|
-
prop.startsWith("@@") &&
|
|
215
|
-
list instanceof List);
|
|
216
|
-
});
|
|
217
|
-
const lists = {};
|
|
218
|
-
if (individuals) {
|
|
219
|
-
lists["@@INDI"] = individuals;
|
|
220
|
-
}
|
|
221
|
-
usedLists.forEach(([key, list]) => {
|
|
222
|
-
const validKey = key;
|
|
223
|
-
if (list) {
|
|
224
|
-
lists[validKey] = list.filter((item) => {
|
|
225
|
-
return Boolean(item.id && refs[item.id]);
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
return lists;
|
|
230
|
-
}
|
|
231
|
-
getDownloadHeader() {
|
|
232
|
-
const newHead = createCommon();
|
|
233
|
-
Object.assign(newHead, this.get("HEAD") ?? {});
|
|
234
|
-
const newSour = createCommon();
|
|
235
|
-
newSour.set("CORP", createCommon());
|
|
236
|
-
newSour.set("CORP.WWW", createCommon());
|
|
237
|
-
newSour.set("NAME", createCommon());
|
|
238
|
-
newSour.set("VERS", createCommon());
|
|
239
|
-
newSour.CORP.value = "TreeViz - The Family Tree Visualiser";
|
|
240
|
-
newSour.CORP.WWW.value = "treeviz.com";
|
|
241
|
-
newSour.NAME.value = "TreeViz - The Family Tree Visualiser";
|
|
242
|
-
newSour.VERS.value = getVersion();
|
|
243
|
-
newHead.set("SOUR", newSour);
|
|
244
|
-
return newHead;
|
|
245
|
-
}
|
|
246
|
-
toFiltered(indis) {
|
|
247
|
-
if (!indis.length) {
|
|
248
|
-
return this;
|
|
249
|
-
}
|
|
250
|
-
const newGedcom = createGedCom();
|
|
251
|
-
const newContent = this.getIndiRelatedLists(indis);
|
|
252
|
-
Object.assign(newGedcom, this, newContent, {
|
|
253
|
-
HEAD: this.getDownloadHeader(),
|
|
254
|
-
});
|
|
255
|
-
return newGedcom;
|
|
256
|
-
}
|
|
257
|
-
toJson(tag, options) {
|
|
258
|
-
if (!options?.indis?.length) {
|
|
259
|
-
return super.toJson(tag, options);
|
|
260
|
-
}
|
|
261
|
-
const newGedcom = createGedCom();
|
|
262
|
-
const newContent = this.getIndiRelatedLists(options.indis);
|
|
263
|
-
Object.assign(newGedcom, this, newContent, {
|
|
264
|
-
HEAD: this.getDownloadHeader(),
|
|
265
|
-
});
|
|
266
|
-
delete options.indis;
|
|
267
|
-
return newGedcom.toJson(tag, options);
|
|
268
|
-
}
|
|
269
|
-
toGedcom(tag, level, options) {
|
|
270
|
-
if (options?.super) {
|
|
271
|
-
return super.toGedcom(tag, level, options);
|
|
272
|
-
}
|
|
273
|
-
const newGedcom = createGedCom();
|
|
274
|
-
if (!options?.original) {
|
|
275
|
-
Object.assign(newGedcom, {
|
|
276
|
-
HEAD: this.getDownloadHeader(),
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
Object.assign(newGedcom, this);
|
|
280
|
-
if (options?.indis?.length) {
|
|
281
|
-
const newContent = this.getIndiRelatedLists(options.indis);
|
|
282
|
-
Object.assign(newGedcom, newContent);
|
|
283
|
-
}
|
|
284
|
-
return newGedcom.toGedcom(tag, level, { ...options, super: true });
|
|
285
|
-
}
|
|
286
|
-
hasTag(tag) {
|
|
287
|
-
const tagName = typeof tag === "string"
|
|
288
|
-
? tag
|
|
289
|
-
: tag?.get("NAME")?.toValue();
|
|
290
|
-
if (!tagName) {
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
return !!this.tagMembers?.[tagName]?.indis?.length;
|
|
294
|
-
}
|
|
295
|
-
hasUnknownAncestor() {
|
|
296
|
-
return !!this.tagMembers?.[CustomTags.UnknownAncestor]?.indis?.length;
|
|
297
|
-
}
|
|
298
|
-
hasIgnoredMember() {
|
|
299
|
-
return !!this.tagMembers?.[CustomTags.IgnoredMember]?.indis?.length;
|
|
300
|
-
}
|
|
301
|
-
hasUnattachedMember() {
|
|
302
|
-
return !!this.tagMembers?.[CustomTags.UnattachedMember]?.indis?.length;
|
|
303
|
-
}
|
|
304
|
-
hasUnknownGivenname() {
|
|
305
|
-
return !!this.tagMembers?.[CustomTags.UnknownGivenname]?.indis?.length;
|
|
306
|
-
}
|
|
307
|
-
hasUnknownSurname() {
|
|
308
|
-
return !!this.tagMembers?.[CustomTags.UnknownSurname]?.indis?.length;
|
|
309
|
-
}
|
|
310
|
-
hasNonRelevant() {
|
|
311
|
-
return this.hasUnknownAncestor() || this.hasUnattachedMember();
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Get all places from the GEDCOM with occurrence counts
|
|
315
|
-
* @returns Record mapping place names to their occurrence count
|
|
316
|
-
*/
|
|
317
|
-
getAllPlaces() {
|
|
318
|
-
const indis = this.indis();
|
|
319
|
-
const allPlaces = {};
|
|
320
|
-
indis?.forEach((indi) => {
|
|
321
|
-
if (!indi.id)
|
|
322
|
-
return;
|
|
323
|
-
const places = indi.getPlaces();
|
|
324
|
-
places.forEach((placeObj) => {
|
|
325
|
-
const placeName = placeObj?.place;
|
|
326
|
-
if (placeName && typeof placeName === "string") {
|
|
327
|
-
allPlaces[placeName] = (allPlaces[placeName] || 0) + 1;
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
return allPlaces;
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* Get all places from the GEDCOM with associated individual IDs
|
|
335
|
-
* @param usedIndis Optional array of individual IDs to filter by
|
|
336
|
-
* @returns Record mapping place names to arrays of individual IDs
|
|
337
|
-
*/
|
|
338
|
-
getAllPlacesWithIndis(usedIndis = []) {
|
|
339
|
-
const indis = this.indis();
|
|
340
|
-
const allPlaces = {};
|
|
341
|
-
indis?.forEach((indi) => {
|
|
342
|
-
if (!indi.id ||
|
|
343
|
-
(usedIndis.length && !usedIndis.includes(indi.id))) {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
const places = indi.getPlaces();
|
|
347
|
-
places.forEach((placeObj) => {
|
|
348
|
-
const placeName = placeObj?.place;
|
|
349
|
-
if (placeName && typeof placeName === "string" && indi.id) {
|
|
350
|
-
if (!allPlaces[placeName]) {
|
|
351
|
-
allPlaces[placeName] = [];
|
|
352
|
-
}
|
|
353
|
-
allPlaces[placeName].push(indi.id);
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
});
|
|
357
|
-
return allPlaces;
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Generate statistics about the GEDCOM file
|
|
361
|
-
* @param individuals Optional list of individuals to calculate statistics for. If not provided, all individuals from the GEDCOM will be used.
|
|
362
|
-
* @returns Object containing various statistics about the GEDCOM data
|
|
363
|
-
*/
|
|
364
|
-
stats(individuals) {
|
|
365
|
-
const indis = individuals ?? this.indis();
|
|
366
|
-
// Build families list based on whether individuals filter is provided
|
|
367
|
-
let families;
|
|
368
|
-
if (individuals) {
|
|
369
|
-
// If individuals filter is provided, filter families by references
|
|
370
|
-
const familyIds = new Set();
|
|
371
|
-
indis?.forEach((indi) => {
|
|
372
|
-
// Add spouse families
|
|
373
|
-
indi.FAMS?.toList()?.forEach((famRef) => {
|
|
374
|
-
const famId = famRef.value;
|
|
375
|
-
if (famId)
|
|
376
|
-
familyIds.add(famId);
|
|
377
|
-
});
|
|
378
|
-
// Add parent families
|
|
379
|
-
indi.FAMC?.toList()?.forEach((famRef) => {
|
|
380
|
-
const famId = famRef.value;
|
|
381
|
-
if (famId)
|
|
382
|
-
familyIds.add(famId);
|
|
383
|
-
});
|
|
384
|
-
});
|
|
385
|
-
families = this.fams()?.filter((fam) => fam.id ? familyIds.has(fam.id) : false);
|
|
386
|
-
}
|
|
387
|
-
else {
|
|
388
|
-
// No filter provided, use all families directly
|
|
389
|
-
families = this.fams();
|
|
390
|
-
} // Calculate statistics
|
|
391
|
-
const totalIndividuals = indis?.length || 0;
|
|
392
|
-
const totalFamilies = families?.length || 0;
|
|
393
|
-
// Count by sex
|
|
394
|
-
let males = 0;
|
|
395
|
-
let females = 0;
|
|
396
|
-
let unknownSex = 0;
|
|
397
|
-
indis?.forEach((indi) => {
|
|
398
|
-
const sex = indi.SEX?.value;
|
|
399
|
-
if (sex === "M")
|
|
400
|
-
males++;
|
|
401
|
-
else if (sex === "F")
|
|
402
|
-
females++;
|
|
403
|
-
else
|
|
404
|
-
unknownSex++;
|
|
405
|
-
});
|
|
406
|
-
// Most common surnames
|
|
407
|
-
const surnames = new Map();
|
|
408
|
-
indis?.forEach((indi) => {
|
|
409
|
-
const name = indi.NAME?.toValue();
|
|
410
|
-
if (name) {
|
|
411
|
-
const match = name.match(/\/(.+?)\//);
|
|
412
|
-
if (match) {
|
|
413
|
-
const surname = match[1];
|
|
414
|
-
surnames.set(surname, (surnames.get(surname) || 0) + 1);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
const topSurnames = Array.from(surnames.entries())
|
|
419
|
-
.sort((a, b) => b[1] - a[1])
|
|
420
|
-
.slice(0, 10)
|
|
421
|
-
.map(([surname, count]) => ({ surname, count }));
|
|
422
|
-
// Most common birth places
|
|
423
|
-
const birthPlaces = new Map();
|
|
424
|
-
indis?.forEach((indi) => {
|
|
425
|
-
const place = indi.BIRT?.PLAC?.value;
|
|
426
|
-
if (place) {
|
|
427
|
-
birthPlaces.set(place, (birthPlaces.get(place) || 0) + 1);
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
const topBirthPlaces = Array.from(birthPlaces.entries())
|
|
431
|
-
.sort((a, b) => b[1] - a[1])
|
|
432
|
-
.slice(0, 10)
|
|
433
|
-
.map(([place, count]) => ({ place, count }));
|
|
434
|
-
// Date range
|
|
435
|
-
const years = [];
|
|
436
|
-
indis?.forEach((indi) => {
|
|
437
|
-
const birthDate = indi.BIRT?.DATE?.toValue();
|
|
438
|
-
if (birthDate) {
|
|
439
|
-
const match = birthDate.match(/\d{4}/);
|
|
440
|
-
if (match) {
|
|
441
|
-
years.push(parseInt(match[0], 10));
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
const deathDate = indi.DEAT?.DATE?.toValue();
|
|
445
|
-
if (deathDate) {
|
|
446
|
-
const match = deathDate.match(/\d{4}/);
|
|
447
|
-
if (match) {
|
|
448
|
-
years.push(parseInt(match[0], 10));
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
const minYear = years.length > 0 ? Math.min(...years) : null;
|
|
453
|
-
const maxYear = years.length > 0 ? Math.max(...years) : null;
|
|
454
|
-
// Average lifespan
|
|
455
|
-
const lifespans = [];
|
|
456
|
-
indis?.forEach((indi) => {
|
|
457
|
-
const birthDate = indi.BIRT?.DATE?.toValue();
|
|
458
|
-
const deathDate = indi.DEAT?.DATE?.toValue();
|
|
459
|
-
if (birthDate && deathDate) {
|
|
460
|
-
const birthMatch = birthDate.match(/\d{4}/);
|
|
461
|
-
const deathMatch = deathDate.match(/\d{4}/);
|
|
462
|
-
if (birthMatch && deathMatch) {
|
|
463
|
-
const birthYear = parseInt(birthMatch[0], 10);
|
|
464
|
-
const deathYear = parseInt(deathMatch[0], 10);
|
|
465
|
-
if (deathYear > birthYear) {
|
|
466
|
-
lifespans.push(deathYear - birthYear);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
const avgLifespan = lifespans.length > 0
|
|
472
|
-
? lifespans.reduce((sum, age) => sum + age, 0) /
|
|
473
|
-
lifespans.length
|
|
474
|
-
: null;
|
|
475
|
-
// First and last person events with type information
|
|
476
|
-
const firstPerson = indis?.getFirstEvent();
|
|
477
|
-
const firstBirth = firstPerson?.BIRT?.toList().index(0);
|
|
478
|
-
const firstDeath = firstPerson?.DEAT?.toList().index(0);
|
|
479
|
-
let firstPersonEvent = null;
|
|
480
|
-
const firstBirthDate = firstBirth?.DATE
|
|
481
|
-
?.rawValue;
|
|
482
|
-
const firstDeathDate = firstDeath?.DATE
|
|
483
|
-
?.rawValue;
|
|
484
|
-
if (firstBirthDate || firstDeathDate) {
|
|
485
|
-
const isBirth = !firstBirthDate ||
|
|
486
|
-
(firstDeathDate && firstDeathDate < firstBirthDate)
|
|
487
|
-
? false
|
|
488
|
-
: true;
|
|
489
|
-
firstPersonEvent = {
|
|
490
|
-
type: isBirth ? "BIRT" : "DEAT",
|
|
491
|
-
event: isBirth ? firstBirth : firstDeath,
|
|
492
|
-
person: firstPerson,
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
const lastPerson = indis?.getLastEvent();
|
|
496
|
-
const lastBirth = lastPerson?.BIRT?.toList().index(0);
|
|
497
|
-
const lastDeath = lastPerson?.DEAT?.toList().index(0);
|
|
498
|
-
let lastPersonEvent = null;
|
|
499
|
-
const lastBirthDate = lastBirth
|
|
500
|
-
?.DATE?.rawValue;
|
|
501
|
-
const lastDeathDate = lastDeath
|
|
502
|
-
?.DATE?.rawValue;
|
|
503
|
-
if (lastBirthDate || lastDeathDate) {
|
|
504
|
-
const isBirth = !lastDeathDate ||
|
|
505
|
-
(lastBirthDate && lastDeathDate < lastBirthDate)
|
|
506
|
-
? true
|
|
507
|
-
: false;
|
|
508
|
-
lastPersonEvent = {
|
|
509
|
-
type: isBirth ? "BIRT" : "DEAT",
|
|
510
|
-
event: isBirth ? lastBirth : lastDeath,
|
|
511
|
-
person: lastPerson,
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
return {
|
|
515
|
-
totalIndividuals,
|
|
516
|
-
totalFamilies,
|
|
517
|
-
byGender: {
|
|
518
|
-
males,
|
|
519
|
-
females,
|
|
520
|
-
unknown: unknownSex,
|
|
521
|
-
},
|
|
522
|
-
dateRange: {
|
|
523
|
-
earliest: minYear,
|
|
524
|
-
latest: maxYear,
|
|
525
|
-
},
|
|
526
|
-
averageLifespan: avgLifespan
|
|
527
|
-
? Math.round(avgLifespan * 10) / 10
|
|
528
|
-
: null,
|
|
529
|
-
topSurnames,
|
|
530
|
-
topBirthPlaces,
|
|
531
|
-
firstPersonEvent,
|
|
532
|
-
lastPersonEvent,
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
export const createGedCom = () => {
|
|
537
|
-
return new GedCom();
|
|
538
|
-
};
|
|
539
|
-
export const isGedcomString = (gedcomString) => {
|
|
540
|
-
return (typeof gedcomString === "string" &&
|
|
541
|
-
gedcomString[0] === "0" &&
|
|
542
|
-
gedcomString
|
|
543
|
-
.split("\n")
|
|
544
|
-
.slice(0, 1000)
|
|
545
|
-
.every((l) => !l || /^\d+\s+/.test(l)));
|
|
546
|
-
};
|
|
547
|
-
/**
|
|
548
|
-
* Validates if a string is a valid GEDCOM file content
|
|
549
|
-
* @param content - The file content to validate
|
|
550
|
-
* @returns An object with `valid` boolean and optional `error` message
|
|
551
|
-
*/
|
|
552
|
-
export const validateGedcomContent = (content) => {
|
|
553
|
-
if (!content) {
|
|
554
|
-
return { valid: false, error: "File is empty" };
|
|
555
|
-
}
|
|
556
|
-
const trimmedContent = content.trim();
|
|
557
|
-
if (!trimmedContent) {
|
|
558
|
-
return { valid: false, error: "File is empty" };
|
|
559
|
-
}
|
|
560
|
-
// Check for common binary file signatures
|
|
561
|
-
const firstBytes = trimmedContent.substring(0, 10);
|
|
562
|
-
const isBinary = firstBytes.startsWith("\x89PNG") || // PNG
|
|
563
|
-
firstBytes.startsWith("\xFF\xD8\xFF") || // JPEG
|
|
564
|
-
firstBytes.startsWith("GIF8") || // GIF
|
|
565
|
-
firstBytes.startsWith("BM") || // BMP
|
|
566
|
-
firstBytes.startsWith("PK") || // ZIP
|
|
567
|
-
firstBytes.startsWith("%PDF") || // PDF
|
|
568
|
-
// eslint-disable-next-line no-control-regex
|
|
569
|
-
/[\x00-\x08\x0E-\x1F]/.test(firstBytes); // Other binary content
|
|
570
|
-
if (isBinary) {
|
|
571
|
-
return {
|
|
572
|
-
valid: false,
|
|
573
|
-
error: "File appears to be a binary file (image, PDF, etc.), not a GEDCOM text file",
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
// Check if content starts with GEDCOM header
|
|
577
|
-
// GEDCOM files must start with "0 HEAD" or "0 head" (case-insensitive)
|
|
578
|
-
const startsWithHeader = /^0\s+(HEAD|head)/i.test(trimmedContent);
|
|
579
|
-
if (!startsWithHeader) {
|
|
580
|
-
return {
|
|
581
|
-
valid: false,
|
|
582
|
-
error: "Invalid GEDCOM file format. File must start with '0 HEAD' record",
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* Turning this off, because it's invalidating multiline gedcom property
|
|
587
|
-
* (I know these kind of GEDCOMs are not standard, but need to allow any user files)
|
|
588
|
-
**/
|
|
589
|
-
// Additional check using the existing isGedcomString logic
|
|
590
|
-
// if (!isGedcomString(trimmedContent)) {
|
|
591
|
-
// return {
|
|
592
|
-
// valid: false,
|
|
593
|
-
// error: "Invalid GEDCOM file format. File contains invalid line formats",
|
|
594
|
-
// };
|
|
595
|
-
// }
|
|
596
|
-
return { valid: true };
|
|
597
|
-
};
|
|
598
|
-
// Constants for merge operation
|
|
599
|
-
const MERGE_ID_INCREMENT_MULTIPLIER = 1000;
|
|
600
|
-
/**
|
|
601
|
-
* Helper function to convert a Common or List value to a string for comparison
|
|
602
|
-
*/
|
|
603
|
-
const valueToString = (value) => {
|
|
604
|
-
if (!value)
|
|
605
|
-
return "";
|
|
606
|
-
return (value.toString?.() ||
|
|
607
|
-
String(value?.toValue?.() || ""));
|
|
608
|
-
};
|
|
609
|
-
/**
|
|
610
|
-
* Merge two GEDCOM objects into a single result using a configurable matching strategy
|
|
611
|
-
* @param targetGedcom - The base GEDCOM (kept as the primary source)
|
|
612
|
-
* @param sourceGedcom - The GEDCOM to be merged into the target
|
|
613
|
-
* @param strategy - Matching strategy: "id" (default) to match by individual ID, or any MultiTag (e.g., "NAME", "BIRT.DATE") to match by that field's value
|
|
614
|
-
* @returns The merged GedComType with all individuals and families combined
|
|
615
|
-
*
|
|
616
|
-
* @example
|
|
617
|
-
* // Merge by ID (individuals with same ID are considered the same person)
|
|
618
|
-
* const merged = await mergeGedcoms(target, source, "id");
|
|
619
|
-
*
|
|
620
|
-
* @example
|
|
621
|
-
* // Merge by NAME (individuals with same name are considered the same person)
|
|
622
|
-
* const merged = await mergeGedcoms(target, source, "NAME");
|
|
623
|
-
*
|
|
624
|
-
* @remarks
|
|
625
|
-
* - Source individuals are always assigned new unique IDs to avoid conflicts
|
|
626
|
-
* - When individuals match by strategy, they are merged (data and relationships combined)
|
|
627
|
-
* - All family relationships (FAMS/FAMC) are preserved with updated ID references
|
|
628
|
-
*/
|
|
629
|
-
export const mergeGedcoms = (targetGedcom, sourceGedcom, strategy = "id") => {
|
|
630
|
-
// Work directly with the target GEDCOM (no serialization needed)
|
|
631
|
-
const mergedGedcom = targetGedcom;
|
|
632
|
-
// Track ID mapping: source ID -> new ID in merged GEDCOM
|
|
633
|
-
const idMap = new Map();
|
|
634
|
-
// Track matching: sourceIndiId -> targetIndiId (for individuals that match by strategy)
|
|
635
|
-
const matchMap = new Map();
|
|
636
|
-
// Get source individuals and families
|
|
637
|
-
const sourceIndis = sourceGedcom.indis();
|
|
638
|
-
const sourceFams = sourceGedcom.fams();
|
|
639
|
-
const targetIndis = mergedGedcom.indis();
|
|
640
|
-
const targetFams = mergedGedcom.fams();
|
|
641
|
-
// Step 1: Identify matches and create ID mappings for individuals
|
|
642
|
-
sourceIndis?.forEach((sourceIndi) => {
|
|
643
|
-
if (!sourceIndi.id)
|
|
644
|
-
return;
|
|
645
|
-
let matchedTargetIndi;
|
|
646
|
-
if (strategy === "id") {
|
|
647
|
-
// Match by ID directly - check if target has this exact ID
|
|
648
|
-
matchedTargetIndi = targetIndis?.item(sourceIndi.id);
|
|
649
|
-
}
|
|
650
|
-
else {
|
|
651
|
-
// Match by specified MultiTag value
|
|
652
|
-
const sourceValueRaw = sourceIndi.get(strategy);
|
|
653
|
-
const sourceValue = valueToString(sourceValueRaw);
|
|
654
|
-
if (sourceValue) {
|
|
655
|
-
// Find target individual with same value
|
|
656
|
-
matchedTargetIndi = targetIndis?.find((targetIndi) => {
|
|
657
|
-
const targetValueRaw = targetIndi.get(strategy);
|
|
658
|
-
const targetValue = valueToString(targetValueRaw);
|
|
659
|
-
return Boolean(targetValue && targetValue === sourceValue);
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
if (matchedTargetIndi && matchedTargetIndi.id) {
|
|
664
|
-
// Found a match - map source ID to existing target ID
|
|
665
|
-
// Note: This means source individual will merge into target individual
|
|
666
|
-
matchMap.set(sourceIndi.id, matchedTargetIndi.id);
|
|
667
|
-
idMap.set(sourceIndi.id, matchedTargetIndi.id);
|
|
668
|
-
}
|
|
669
|
-
else {
|
|
670
|
-
// No match - need to create a new unique ID
|
|
671
|
-
const baseId = sourceIndi.id;
|
|
672
|
-
let newId = baseId;
|
|
673
|
-
let counter = 1;
|
|
674
|
-
// Generate unique ID that doesn't conflict with target
|
|
675
|
-
while (targetIndis?.item(newId) || idMap.has(newId)) {
|
|
676
|
-
// Extract number from ID like @I123@ and increment
|
|
677
|
-
const numMatch = baseId.match(/\d+/);
|
|
678
|
-
const prefix = baseId.match(/^@[A-Z]+/)?.[0] || "@I";
|
|
679
|
-
const baseNum = numMatch ? parseInt(numMatch[0]) : 1;
|
|
680
|
-
newId =
|
|
681
|
-
`${prefix}${baseNum + counter * MERGE_ID_INCREMENT_MULTIPLIER}@`;
|
|
682
|
-
counter++;
|
|
683
|
-
}
|
|
684
|
-
idMap.set(sourceIndi.id, newId);
|
|
685
|
-
}
|
|
686
|
-
});
|
|
687
|
-
// Step 2: Map family IDs and match families with same members
|
|
688
|
-
const famMatchMap = new Map(); // source family ID -> target family ID
|
|
689
|
-
sourceFams?.forEach((sourceFam) => {
|
|
690
|
-
if (!sourceFam.id)
|
|
691
|
-
return;
|
|
692
|
-
// Get the member IDs for this source family
|
|
693
|
-
const sourceHusbId = sourceFam.HUSB?.value;
|
|
694
|
-
const sourceWifeId = sourceFam.WIFE?.value;
|
|
695
|
-
const sourceChildIds = sourceFam.CHIL?.toList()
|
|
696
|
-
?.map((c) => c.value)
|
|
697
|
-
.filter(Boolean) || [];
|
|
698
|
-
// Map to their final IDs (after individual matching by strategy)
|
|
699
|
-
const finalHusbId = sourceHusbId
|
|
700
|
-
? idMap.get(sourceHusbId)
|
|
701
|
-
: undefined;
|
|
702
|
-
const finalWifeId = sourceWifeId
|
|
703
|
-
? idMap.get(sourceWifeId)
|
|
704
|
-
: undefined;
|
|
705
|
-
const finalChildIds = sourceChildIds
|
|
706
|
-
.map((id) => idMap.get(id))
|
|
707
|
-
.filter(Boolean);
|
|
708
|
-
// Try to find a matching family in target that has the same members (after remapping)
|
|
709
|
-
let matchedTargetFam;
|
|
710
|
-
targetFams?.forEach((targetFam) => {
|
|
711
|
-
if (matchedTargetFam)
|
|
712
|
-
return; // Already found a match
|
|
713
|
-
const targetHusbId = targetFam.HUSB?.value;
|
|
714
|
-
const targetWifeId = targetFam.WIFE?.value;
|
|
715
|
-
const targetChildIds = targetFam.CHIL?.toList()
|
|
716
|
-
?.map((c) => c.value)
|
|
717
|
-
.filter(Boolean) || [];
|
|
718
|
-
// Check if husband matches (either both undefined or same final ID)
|
|
719
|
-
const husbMatch = (!finalHusbId && !targetHusbId) || finalHusbId === targetHusbId;
|
|
720
|
-
// Check if wife matches (either both undefined or same final ID)
|
|
721
|
-
const wifeMatch = (!finalWifeId && !targetWifeId) || finalWifeId === targetWifeId;
|
|
722
|
-
// For a family to match, both husband and wife must match
|
|
723
|
-
// (if one is undefined in both, that's also a match)
|
|
724
|
-
if (husbMatch && wifeMatch) {
|
|
725
|
-
// Also check children overlap
|
|
726
|
-
const childOverlap = finalChildIds.filter((id) => id && targetChildIds.includes(id)).length;
|
|
727
|
-
const totalUniqueChildren = new Set([
|
|
728
|
-
...finalChildIds,
|
|
729
|
-
...targetChildIds,
|
|
730
|
-
]).size;
|
|
731
|
-
// Match if:
|
|
732
|
-
// 1. At least one spouse is present (to avoid matching empty families), OR
|
|
733
|
-
// 2. At least 50% of children overlap
|
|
734
|
-
const hasSpouses = finalHusbId ||
|
|
735
|
-
finalWifeId ||
|
|
736
|
-
targetHusbId ||
|
|
737
|
-
targetWifeId;
|
|
738
|
-
const hasMatchingChildren = totalUniqueChildren > 0 &&
|
|
739
|
-
childOverlap / totalUniqueChildren >= 0.5;
|
|
740
|
-
if (hasSpouses || hasMatchingChildren) {
|
|
741
|
-
matchedTargetFam = targetFam;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
});
|
|
745
|
-
if (matchedTargetFam && matchedTargetFam.id) {
|
|
746
|
-
// Found a matching family - use the target family ID
|
|
747
|
-
famMatchMap.set(sourceFam.id, matchedTargetFam.id);
|
|
748
|
-
idMap.set(sourceFam.id, matchedTargetFam.id);
|
|
749
|
-
}
|
|
750
|
-
else {
|
|
751
|
-
// No match - generate a new unique family ID
|
|
752
|
-
const baseId = sourceFam.id;
|
|
753
|
-
let newId = baseId;
|
|
754
|
-
let counter = 1;
|
|
755
|
-
// Generate unique family ID
|
|
756
|
-
while (targetFams?.item(newId) || idMap.has(newId)) {
|
|
757
|
-
const numMatch = baseId.match(/\d+/);
|
|
758
|
-
const prefix = baseId.match(/^@[A-Z]+/)?.[0] || "@F";
|
|
759
|
-
const baseNum = numMatch ? parseInt(numMatch[0]) : 1;
|
|
760
|
-
newId =
|
|
761
|
-
`${prefix}${baseNum + counter * MERGE_ID_INCREMENT_MULTIPLIER}@`;
|
|
762
|
-
counter++;
|
|
763
|
-
}
|
|
764
|
-
idMap.set(sourceFam.id, newId);
|
|
765
|
-
}
|
|
766
|
-
});
|
|
767
|
-
// Step 3: Clone and remap individuals from source
|
|
768
|
-
const clonedIndis = new Map();
|
|
769
|
-
sourceIndis?.forEach((sourceIndi) => {
|
|
770
|
-
if (!sourceIndi.id)
|
|
771
|
-
return;
|
|
772
|
-
const newId = idMap.get(sourceIndi.id);
|
|
773
|
-
if (!newId)
|
|
774
|
-
return;
|
|
775
|
-
// Clone the individual
|
|
776
|
-
const cloned = sourceIndi.clone(false);
|
|
777
|
-
cloned.id = newId;
|
|
778
|
-
cloned.setGedcom(mergedGedcom);
|
|
779
|
-
clonedIndis.set(sourceIndi.id, cloned);
|
|
780
|
-
});
|
|
781
|
-
// Step 4: Clone and remap families from source
|
|
782
|
-
const clonedFams = new Map();
|
|
783
|
-
sourceFams?.forEach((sourceFam) => {
|
|
784
|
-
if (!sourceFam.id)
|
|
785
|
-
return;
|
|
786
|
-
const newFamId = idMap.get(sourceFam.id);
|
|
787
|
-
if (!newFamId)
|
|
788
|
-
return;
|
|
789
|
-
// Clone the family
|
|
790
|
-
const clonedFam = sourceFam.clone(false);
|
|
791
|
-
clonedFam.id = newFamId;
|
|
792
|
-
clonedFam.setGedcom(mergedGedcom);
|
|
793
|
-
clonedFams.set(sourceFam.id, clonedFam);
|
|
794
|
-
});
|
|
795
|
-
// Step 5: Update FAMS and FAMC references in cloned individuals
|
|
796
|
-
clonedIndis.forEach((clonedIndi, originalId) => {
|
|
797
|
-
const sourceIndi = sourceIndis?.item(originalId);
|
|
798
|
-
if (!sourceIndi)
|
|
799
|
-
return;
|
|
800
|
-
// Clear and rebuild FAMS references with remapped IDs
|
|
801
|
-
clonedIndi.remove("FAMS");
|
|
802
|
-
const sourceFAMS = sourceIndi.FAMS?.toList();
|
|
803
|
-
sourceFAMS?.forEach((famRef) => {
|
|
804
|
-
const oldFamId = famRef.value;
|
|
805
|
-
if (oldFamId) {
|
|
806
|
-
const newFamId = idMap.get(oldFamId);
|
|
807
|
-
if (newFamId) {
|
|
808
|
-
const newFamRef = createCommon(mergedGedcom, undefined, clonedIndi);
|
|
809
|
-
newFamRef.value = newFamId;
|
|
810
|
-
clonedIndi.assign("FAMS", newFamRef, true);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
});
|
|
814
|
-
// Clear and rebuild FAMC references with remapped IDs
|
|
815
|
-
clonedIndi.remove("FAMC");
|
|
816
|
-
const sourceFAMC = sourceIndi.FAMC?.toList();
|
|
817
|
-
sourceFAMC?.forEach((famRef) => {
|
|
818
|
-
const oldFamId = famRef.value;
|
|
819
|
-
if (oldFamId) {
|
|
820
|
-
const newFamId = idMap.get(oldFamId);
|
|
821
|
-
if (newFamId) {
|
|
822
|
-
const newFamRef = createCommon(mergedGedcom, undefined, clonedIndi);
|
|
823
|
-
newFamRef.value = newFamId;
|
|
824
|
-
clonedIndi.assign("FAMC", newFamRef, true);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
});
|
|
828
|
-
});
|
|
829
|
-
// Step 6: Update HUSB, WIFE, and CHIL references in cloned families
|
|
830
|
-
clonedFams.forEach((clonedFam, originalFamId) => {
|
|
831
|
-
const sourceFam = sourceFams?.item(originalFamId);
|
|
832
|
-
if (!sourceFam)
|
|
833
|
-
return;
|
|
834
|
-
// Update HUSB reference
|
|
835
|
-
const sourceHusb = sourceFam.HUSB?.value;
|
|
836
|
-
if (sourceHusb) {
|
|
837
|
-
const newHusbId = idMap.get(sourceHusb);
|
|
838
|
-
if (newHusbId) {
|
|
839
|
-
const newHusbRef = createCommon(mergedGedcom, undefined, clonedFam);
|
|
840
|
-
newHusbRef.value = newHusbId;
|
|
841
|
-
clonedFam.set("HUSB", newHusbRef);
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
// Update WIFE reference
|
|
845
|
-
const sourceWife = sourceFam.WIFE?.value;
|
|
846
|
-
if (sourceWife) {
|
|
847
|
-
const newWifeId = idMap.get(sourceWife);
|
|
848
|
-
if (newWifeId) {
|
|
849
|
-
const newWifeRef = createCommon(mergedGedcom, undefined, clonedFam);
|
|
850
|
-
newWifeRef.value = newWifeId;
|
|
851
|
-
clonedFam.set("WIFE", newWifeRef);
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
// Update CHIL references
|
|
855
|
-
clonedFam.remove("CHIL");
|
|
856
|
-
const sourceChildren = sourceFam.CHIL?.toList();
|
|
857
|
-
sourceChildren?.forEach((childRef) => {
|
|
858
|
-
const oldChildId = childRef.value;
|
|
859
|
-
if (oldChildId) {
|
|
860
|
-
const newChildId = idMap.get(oldChildId);
|
|
861
|
-
if (newChildId) {
|
|
862
|
-
const newChildRef = createCommon(mergedGedcom, undefined, clonedFam);
|
|
863
|
-
newChildRef.value = newChildId;
|
|
864
|
-
clonedFam.assign("CHIL", newChildRef, true);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
});
|
|
868
|
-
});
|
|
869
|
-
// Step 7: Add or merge individuals into target GEDCOM
|
|
870
|
-
clonedIndis.forEach((clonedIndi, originalId) => {
|
|
871
|
-
const matchedTargetId = matchMap.get(originalId);
|
|
872
|
-
if (matchedTargetId) {
|
|
873
|
-
// This individual matches an existing one - merge data and relationships
|
|
874
|
-
const targetIndi = mergedGedcom.indis()?.item(matchedTargetId);
|
|
875
|
-
if (targetIndi) {
|
|
876
|
-
// Merge without overriding existing data
|
|
877
|
-
// If using a strategy field (not "id"), avoid merging that field since it's already the same
|
|
878
|
-
const avoidKeys = strategy !== "id" ? [strategy] : [];
|
|
879
|
-
targetIndi.merge(clonedIndi, false, avoidKeys);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
else {
|
|
883
|
-
// This is a new individual - add it to merged GEDCOM
|
|
884
|
-
if (clonedIndi.id) {
|
|
885
|
-
mergedGedcom.indis()?.item(clonedIndi.id, clonedIndi);
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
// Step 8: Add or merge families into target GEDCOM
|
|
890
|
-
clonedFams.forEach((clonedFam, originalFamId) => {
|
|
891
|
-
if (!clonedFam.id)
|
|
892
|
-
return;
|
|
893
|
-
const matchedTargetFamId = famMatchMap.get(originalFamId);
|
|
894
|
-
if (matchedTargetFamId) {
|
|
895
|
-
// This family matches an existing one - merge children and data
|
|
896
|
-
const targetFam = mergedGedcom.fams()?.item(matchedTargetFamId);
|
|
897
|
-
if (targetFam) {
|
|
898
|
-
// Merge family data without overriding
|
|
899
|
-
targetFam.merge(clonedFam, false);
|
|
900
|
-
// Note: Children from clonedFam are already added via merge above
|
|
901
|
-
// The CHIL references in clonedFam point to the correct remapped IDs
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
else {
|
|
905
|
-
// This is a new family - add it to merged GEDCOM
|
|
906
|
-
mergedGedcom.fams()?.item(clonedFam.id, clonedFam);
|
|
907
|
-
}
|
|
908
|
-
});
|
|
909
|
-
return mergedGedcom;
|
|
910
|
-
};
|